ممنون. ابزار یاهو هم امکان آنالیز (LoggingType ) را دارد. فقط در اینجا چون مد نظر من نبوده به none تنظیم شده.
این مشکل تو کوکیهای یاهو هم هست. پس یه خاطر خودشون هم شده درستش می کنن!
مطالب
تگ گذاری در کامنتها
در محیطهای برنامه نویسی مدرن و امروزی، استفاده از تگها در کامنتها (CommentTag) رواج
بسیاری دارد که یکی از معروفترین این تگها، تگ TODO است. این نوع تگها که عموما به همراه یک توضیح کوتاه یا عنوان به کار میروند، برای این است که
بتوانیم از طریق ابزارهایی که ادیتورها در اختیارمان قرار میدهند، آنها را پیدا کنیم. حتی
در سیستمهای لینوکسی میتوان با دستور Grep به جست و جوی آنها پرداخت. عموما تگها با حروف بزرگ نوشته میشوند؛ ولی اجباری در آن نیست ولی رعایت آن بهتر است. نامهای دیگری که برای این تگها به کار میرود Token و Codetag میباشد. از
معروفترین تگ ها، میتوان به تگهای زیر اشاره کرد:
TODO : معروفترین تگ شناخته شدهاست و میتوان گفت در اکثر اوقات به جای بقیه هم استفاده میشود. چون معنای دیگر کامنتها را نیز در بر میگیرد. این نوع کامنت به شما میگویند که این کد نیاز دارد در یک زمان معین و سر فرصت به آن رسیدگی شود. به عنوان مثال بهتر است ویژگی خاصی را به کدی اضافه کرد، یا مورد خاصی بهتر است در آن پیاده سازی شود و یا نیاز به ویرایش خاصی دارد که پیاده سازی آن منفعتی دارد و این کامنت برای این است که به برنامه نویس یادآوری کند تا در دفعات آتی به این کد رسیدگی کند. سپس در دفعات آتی برنامه نویس میتواند با استفاده از ابزاری که ادیتور در اختیار وی قرار میدهد، این نوع کامنتها را پیدا کند.
FIXME : این نوع تگ همانند بالاست، ولی اجبار بیشتری در اصلاح از خود نشان میدهد و ترتیب و اهمیت بالاتری دارد. عموما کدهایی که با این نوع کامنتها مزین میشوند، دارای طراحی بد یا موقتی هستند که باید در آینده آنها را اصلاح کرد.
UNDONE : این تگ برای اصلاح یا تغییر نیست. ولی به شما میگوید که قبلا این کد چگونه بوده است و چه تغییراتی کرده است. قبلا چه چیزهایی در کد پیاده سازی شده بوده است که الان در کد وجود ندارد و چرا حذف شده است.
HACK : گاهی اوقات در کدها، باگ هایی رخ میدهند که مجبور به استفاده از راههای غیرعادی برای رفع آن میشوید. این نوع روشهای رفع مشکل، روشها و راه حلهای مناسبی نیستند؛ ولی میتوانند به طور موقت و در زمان سریعتری پاسخگوی ما باشند. برنامه نویس بعد از رفع مشکل، با درج این نوع کامنت، در آینده به خود یادآوری میکند که این کد نیاز به راه حل مناسبتری دارد.
BUGBUG : این کامنت توسط برنامه نویس کد مربوطه درج میشود و مربوط به زمانی است که برنامه نویس کد را نوشته است، ولی اطمینانی از صحت آن ندارد. پس برنامه نویس نیاز دارد اطلاعات بیشتری را در مورد این مسئله بیابد.
XXX : به برنامه نویس هشدار میدهد که این کد راه حلهای نادرستی دارد و احتمالا بر اساس اطلاعات نادرستی این کد شکل گرفته است، ولی در حال حاضر کار میکند.
در ویژوال استادیو، پنل taskList برای نمایش این تگها به کار میرود و از تگهای HACK,UNDONE و TODO به طور پیش فرض پشتیبانی میکند. در صورتی که تمایل دارید تگهای اضافهتری داشته باشید یا ترتیب اولویت نمایش تگها در پنل taskList را تغییر دهید، مسیر زیر را طی کنید:
در اندروید استادیو هم دو تگ اول لیست پشتیبانی میشوند. در اندروید استادیو شما میتوانید برای todo هایتان الگو و فیلتر تعریف کنید. برای اینکار ابتدای ادیتور را باز کرده و در بخش Editor گزینه Todo را انتخاب کنید. در لیست بالا میتوانید یک نمونه الگو برای todo خاص خود اضافه کنید. به عنوان مثال تگهای نامبرده در بالا را اضافه کنید و برای آن آیکن و نحوه رنگبندی و قلم و ... را برای نمایش آن انتخاب کنید.
بعد از ذکر نام تگ، تاریخ را بر اساس سال، ماه و روز وارد کرده و سپس نام شخصی که این کامنت را قرار داده است و در ادامه عنوان مناسبی را که گویای مطلب باشد، انتخاب کنید. در خطهای بعدی هم توضیح کوتاهی که مدنظر شماست. در این حالت با استفاده از ابزار unix grep میتوانید گزارش گیری مناسبی هم داشته باشید.
TODO : معروفترین تگ شناخته شدهاست و میتوان گفت در اکثر اوقات به جای بقیه هم استفاده میشود. چون معنای دیگر کامنتها را نیز در بر میگیرد. این نوع کامنت به شما میگویند که این کد نیاز دارد در یک زمان معین و سر فرصت به آن رسیدگی شود. به عنوان مثال بهتر است ویژگی خاصی را به کدی اضافه کرد، یا مورد خاصی بهتر است در آن پیاده سازی شود و یا نیاز به ویرایش خاصی دارد که پیاده سازی آن منفعتی دارد و این کامنت برای این است که به برنامه نویس یادآوری کند تا در دفعات آتی به این کد رسیدگی کند. سپس در دفعات آتی برنامه نویس میتواند با استفاده از ابزاری که ادیتور در اختیار وی قرار میدهد، این نوع کامنتها را پیدا کند.
FIXME : این نوع تگ همانند بالاست، ولی اجبار بیشتری در اصلاح از خود نشان میدهد و ترتیب و اهمیت بالاتری دارد. عموما کدهایی که با این نوع کامنتها مزین میشوند، دارای طراحی بد یا موقتی هستند که باید در آینده آنها را اصلاح کرد.
UNDONE : این تگ برای اصلاح یا تغییر نیست. ولی به شما میگوید که قبلا این کد چگونه بوده است و چه تغییراتی کرده است. قبلا چه چیزهایی در کد پیاده سازی شده بوده است که الان در کد وجود ندارد و چرا حذف شده است.
HACK : گاهی اوقات در کدها، باگ هایی رخ میدهند که مجبور به استفاده از راههای غیرعادی برای رفع آن میشوید. این نوع روشهای رفع مشکل، روشها و راه حلهای مناسبی نیستند؛ ولی میتوانند به طور موقت و در زمان سریعتری پاسخگوی ما باشند. برنامه نویس بعد از رفع مشکل، با درج این نوع کامنت، در آینده به خود یادآوری میکند که این کد نیاز به راه حل مناسبتری دارد.
BUGBUG : این کامنت توسط برنامه نویس کد مربوطه درج میشود و مربوط به زمانی است که برنامه نویس کد را نوشته است، ولی اطمینانی از صحت آن ندارد. پس برنامه نویس نیاز دارد اطلاعات بیشتری را در مورد این مسئله بیابد.
// BUGBUG: I'm sure these GUIDs are defined somewhere but I'm not sure which library contains them, so defining them here. DEFINE_GUID(IID_IFoo, 0x12345678,0x1234,0x1234,0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12);
XXX : به برنامه نویس هشدار میدهد که این کد راه حلهای نادرستی دارد و احتمالا بر اساس اطلاعات نادرستی این کد شکل گرفته است، ولی در حال حاضر کار میکند.
در ویژوال استادیو، پنل taskList برای نمایش این تگها به کار میرود و از تگهای HACK,UNDONE و TODO به طور پیش فرض پشتیبانی میکند. در صورتی که تمایل دارید تگهای اضافهتری داشته باشید یا ترتیب اولویت نمایش تگها در پنل taskList را تغییر دهید، مسیر زیر را طی کنید:
Tools>Options>Environment>Task List
در اندروید استادیو هم دو تگ اول لیست پشتیبانی میشوند. در اندروید استادیو شما میتوانید برای todo هایتان الگو و فیلتر تعریف کنید. برای اینکار ابتدای ادیتور را باز کرده و در بخش Editor گزینه Todo را انتخاب کنید. در لیست بالا میتوانید یک نمونه الگو برای todo خاص خود اضافه کنید. به عنوان مثال تگهای نامبرده در بالا را اضافه کنید و برای آن آیکن و نحوه رنگبندی و قلم و ... را برای نمایش آن انتخاب کنید.
در لیست پایینی که بخش فیلترهاست، میتوانید یک فیلتر را تعریف کنید تا بر اساس این فیلتر مشخص کنید که چه todo هایی نمایش یابند. برای فیلتر کردن در در پنل todo، در نوار ابزار، آیکن قیفی شکل را کلیک کند تا لیست فیلترها نمایش یابند.
نحوه صحیح قرار دادن یک todo به شکل زیر است:
// TODO:2008-12-06:johnc:Add support for negative offsets. // While it is unlikely that we get a negative offset, it can // occur if the garbage collector runs out of space.
تقریبا تمام برنامهها نیاز دارند فرمهای مخصوصی را داشته باشند. به همین جهت در این قسمت، برنامهی نمایش لیست فیلمها را که تا این مرحله تکمیل کردیم، با افزودن تعدادی فرم بهبود میبخشیم؛ مانند فرم لاگین، فرم ثبت نام، فرمی برای ثبت و ویرایش فیلمها و یک فرم جستجوی سریع در لیست فیلمهای موجود.
ایجاد فرم لاگین
فرم لاگینی را که به برنامهی نمایش لیست فیلمهای تکمیل شدهی تا قسمت 17، اضافه خواهیم کرد، یک فرم بوت استرپی است و میتوانید جزئیات بیشتر مزین سازی المانهای این نوع فرمها را با کلاسهای بوت استرپ، در مطلب «کار با شیوهنامههای فرمها در بوت استرپ 4» مطالعه کنید.
در ابتدا فایل جدید src\components\loginForm.jsx را ایجاد کرده و سپس توسط میانبرهای imrc و cc در VSCode، ساختار ابتدایی کامپوننت جدید LoginForm را ایجاد میکنیم:
در ادامه یک Route جدید را در فایل app.js برای این فرم، با مسیر login/ و کامپوننت LoginForm، در ابتدای Switch موجود، تعریف میکنیم:
پس از تعریف این مسیریابی، نیاز است لینک آنرا نیز به منوی راهبری سایت اضافه کنیم. به همین جهت در فایل navBar.jsx که آنرا در قسمت قبل تکمیل کردیم، در انتهای لیست موجود و پس از Rentals، لینک لاگین را نیز قرار میدهیم:
که در نهایت حاصل این تغییرات، به صورت زیر در مرورگر ظاهر میشود:
اکنون نوبت به افزودن فرم بوت استرپی لاگین به فایل loginForm.jsx رسیدهاست:
توضیحات:
- ابتدا المان form به صفحه اضافه میشود.
- سپس هر ورودی، داخل یک div با کلاس form-group، محصور میشود. کار آن تبدیل یک برچسب و فیلد ورودی، به یک گروه از ورودیهای بوت استرپ است.
- در اینجا هر برچسب دارای یک ویژگی for است. اما چون قرار است عبارات jsx، به معادلهای جاوا اسکریپتی ترجمه شوند، نمیتوان از واژهی کلیدی for در اینجا استفاده کرد. به همین جهت از معادل react ای آن که htmlFor است، در کدهای فوق استفاده کردهایم؛ شبیه به نکتهای که در مورد تبدیل ویژگی class به className وجود دارد. مقدار هر ویژگی htmlFor نیز به id فیلد ورودی متناظر با آن تنظیم میشود. به این ترتیب اگر کاربر بر روی این برچسب کلیک کرده و آنرا انتخاب کند، فیلد متناظر با آن، دارای focus میشود.
- فیلدهای ورودی نیز دارای کلاس form-control هستند.
با این خروجی نهایی در مرورگر:
مدیریت ارسال فرمها
به صورت پیش فرض و استاندارد، دکمهی افزوده شدهی به المان form، سبب ارسال اطلاعات آن به سرور و سپس بارگذاری کامل صفحه میشود. این رفتاری نیست که در یک برنامهی SPA مدنظر باشد. برای مدیریت این حالت، میتوان از رخداد onSubmit هر المان فرم، استفاده کرد:
در اینجا یک متد رویدادگردان را برای رخداد onSubmit تعریف کردهایم که توسط آن رخداد جاری، دریافت و متد preventDefault آن فراخوانی میشود تا دیگر پس از کلیک بر روی دکمهی submit، حالت پیشفرض و استاندارد full page reload و post back به سمت سرور، رخ ندهد.
دسترسی مستقیم به المانهای فرمها
پس از فراخوانی متد preventDefault، کار مدیریت ارسال فرم به سرور را باید خودمان مدیریت کنیم و دیگر رخداد full post back استاندارد به سمت سرور را نخواهیم داشت. در جاوا اسکریپت خالص برای دریافت مقادیر وارد شدهی توسط کاربر میتوان نوشت:
اما در React و کدهای یک کامپوننت، نباید ارجاع مستقیمی را به شیء document و DOM اصلی مرورگر داشته باشیم. در برنامههای React هیچگاه نباید با شیء document کار کرد؛ چون کل فلسفهی آن ایجاد یک abstraction بر فراز DOM اصلی مرورگر است که به آن DOM مجازی گفته میشود. به این ترتیب مدیریت برنامه و همچنین آزمون نویسی برای آن نیز سادهتر میشود. اما اگر واقعا نیاز به دسترسی به یک المان DOM در React وجود داشت، چه باید کرد؟
برای دسترسی به یک المان DOM در React، باید یک reference را به آن نسبت داد. برای این منظور یک خاصیت جدید را در سطح کلاس کامپوننت، ایجاد کرده و آنرا با React.RefObject، مقدار دهی اولیه میکنیم:
سپس ویژگی ref المان مدنظر را به این RefObject تنظیم میکنیم:
اکنون زمان submit فرم، اگر نیاز به مقدار username وجود داشت، میتوان توسط خاصیت ارجاعی username تعریف شده، به خاصیت current آن که DOM element مدنظر را بازگشت میدهد، دسترسی یافت و مانند مثال زیر، مقدار آنرا مورد استفاده قرار داد:
در life-cycle hook ای به نام componentDidMount که پس از رندر کامپوننت در DOM فراخوانی میشود، میتوان توسط RefObject تعریف شده، به شیء current که معادل DOM Element متناظر است، دسترسی یافت و سپس متد focus آنرا فراخوانی کرد. در این حالت در اولین بار نمایش فرم، یک چنین تصویری حاصل میشود:
البته روش بهتری نیز برای انجام اینکار وجود دارد. المانهای JSX دارای ویژگی autoFocus نیز هستند که دقیقا همین کار را انجام میدهد:
برای آزمایش آن، قطعه کد componentDidMount را کامنت کرده و برنامه را اجرا کنید.
تبدیل المانهای فرمها به Controlled elements
در بسیاری از اوقات، فرمهای ما state خود را از سرور دریافت میکنند. فرض کنید که در حال ایجاد یک فرم ثبت اطلاعات فیلمها هستیم. در این حالت باید بر اساس id فیلم، اطلاعات آن را از سرور دریافت و در state ذخیره کرد؛ سپس فیلدهای فرم را بر اساس آن مقدار دهی اولیه کرد. برای نمونه در فرم لاگین میتوان state را با شیء account، به صورت زیر مقدار دهی اولیه کرد:
تا اینجا فیلدهای فرم لاگین، از این state مطلع نبوده و تغییرات دادههای ورودی در آنها، به شیء account منعکس نمیشوند. علت اصلی هم اینجا است که هر کدام از فیلدهای ورودی در React، دارای state خاص خود بوده و مستقل از state کامپوننت جاری هستند. برای رفع این مشکل باید آنها را تبدیل به controlled element هایی کرد که دارای state خاص خود نبوده، تمام اطلاعات مورد نیاز خود را از طریق props دریافت میکنند و تغییرات در دادههای خود را از طریق صدور رخدادهایی اطلاع رسانی میکنند. برای اینکار باید مراحل زیر طی شوند:
ابتدا ویژگی value فیلد برای مثال username را به خاصیت username شیء account موجود در state متصل میکنیم:
به این ترتیب دیگر این المان، state خاص خود را نداشته و از طریق props، مقادیر خود را دریافت میکند. تا اینجا username، به رشتهی خالی دریافتی از شیء state و خاصیت account آن، به صورت یک طرفه متصل شدهاست. یعنی زمانیکه فرم نمایش داده میشود، دارای یک مقدار خالی است. برای اینکه تغییرات رخدادهی در این المان را به state منعکس کرد، باید رخداد change آنرا مدیریت نمود. به این ترتیب زمانیکه کاربری اطلاعاتی را در اینجا وارد میکند، رخداد change صادر شده و پس از آن میتوان اطلاعات وارد شده را دریافت و state را به روز رسانی کرد. به روز رسانی state نیز سبب رندر مجدد فرم میشود. بنابراین فیلدهای ورودی، با اطلاعات state جدید، به روز رسانی و رندر میشوند. به همین جهت ابتدا رویداد onChange را به فیلد username اضافه کرده:
و متد مدیریت کنندهی آنرا به صورت زیر تعریف میکنیم:
در اینجا، هدف به روز رسانی this.state.account، بر اساس رخداد رسیده (پارامتر e) است و چون نمیتوان state را مستقیما به روز رسانی کرد، ابتدا یک clone از آن را تهیه میکنیم. سپس توسط e.currentTarget به المان در حال به روز رسانی دسترسی یافته و مقدار آنرا به مقدار خاصیت username انتساب میدهیم. در آخر state را بر اساس این تغییرات، به روز رسانی میکنیم. این انعکاس در state را توسط افزونهی react developer tools هم میتوان مشاهده کرد:
مدیریت دریافت اطلاعات چندین فیلد ورودی
تا اینجا موفق شدیم اطلاعات state را به تغییرات فیلد username در فرم لاگین متصل کنیم؛ اما فیلد password را چگونه باید مدیریت کرد؟ برای اینکه تمام این مراحل را مجددا تکرار نکنیم، میتوان از مقدار دهی پویای خواص در جاوا اسکریپت که توسط [] انجام میشود استفاده کرد:
البته برای اینکه این قطعه کد کار کند، نیاز است ویژگی name فیلدهای ورودی را نیز تنظیم کرد تا e.currentTarget.name، به نام یکی از خواص شیء account تعریف شدهی در state اشاره کند. برای نمونه فیلد کلمهی عبور، ابتدا دارای ویژگی value متصل به خاصیت password شیء account موجود در state میشود. سپس تغییرات آن توسط رویداد onChange، به متد handleChange منتقل شده و خاصیت name آن نیز مقدار دهی شدهاست تا مقدار دهی پویای خواص، در این متد میسر شود:
که در نهایت سبب مقدار دهی صحیح state، با هر دو فیلد تغییر یافته میشود:
یک نکته: میتوان توسط Object Destructuring، تکرار e.currentTarget را حذف کرد:
ما از شیء e دریافتی، تنها به خاصیت currentTarget آن نیاز داریم. بنابراین آنرا از طریق Object Destructuring در همان پارامتر ورودی متد جاری دریافت کرده و سپس آنرا به نام input، تغییر نام میدهیم.
آشنایی با خطاهای متداول دریافتی در حین کار با فرمها
فرض کنید خاصیت username را از شیء account موجود در state حذف کردهایم. در زمان نمایش ابتدایی فرم، خطایی را دریافت نخواهیم کرد، اما اگر اطلاعاتی را در آن وارد کنیم، بلافاصله در کنسول توسعه دهندگان مرورگر چنین اخطاری ظاهر میشود:
چون خاصیت username را حذف کردهایم، اینبار که در textbox مقداری را وارد میکنیم، سبب انتساب undefined و یا null به مقدار المان خواهد شد. در این حالت React چنین المانی را به صورت controlled element درنظر نمیگیرد و دارای state خاص خودش خواهد بود. به همین جهت عنوان میکند که بین یک المان کنترل شده و نشده، یکی را انتخاب کنید.
دقیقا چنین اخطاری را با ورود null/undefined بجای "" در حین مقدار دهی اولیهی username در شیء account نیز دریافت خواهیم کرد:
بنابراین به عنوان یک قاعده در فرمهای React، المانهای یک فرم را باید توسط یک "" مقدار دهی اولیه کرد و یا با مقداری که از سمت سرور دریافت میشود.
ایجاد یک کامپوننت ورود اطلاعات با قابلیت استفادهی مجدد
هر چند در پیاده سازی فعلی سعی کردیم با بکارگیری مقداردهی پویای خواص اشیاء، تکرار کدها را کاهش دهیم، اما باز هم به ازای هر فیلد ورودی باید این مسایل تکرار شوند:
- ایجاد یک div با کلاسهای بوت استرپی.
- ایجاد label و همچنین فیلد ورودی.
- در اینجا مقدار htmlFor باید با مقدار id فیلد ورودی یکی باشد.
- مقدار دهی ویژگیهای value و onChange نیز باید تکرار شوند.
بنابراین بهتر است این تعاریف را استخراج و به یک کامپوننت با قابلیت استفادهی مجدد منتقل کرد. به همین جهت فایل جدید src\components\common\input.jsx را در پوشهی common ایجاد کرده و سپس توسط میانبرهای imrc و sfc، این کامپوننت تابعی بدون حالت را تکمیل میکنیم:
در اینجا کل تگ div مرتبط با username را از کامپوننت فرم لاگین cut کرده و در اینجا در قسمت return، قرار دادهایم. سپس شروع به تبدیل مقادیر قبلی به مقادیری که قرار است از props تامین شوند، کردهایم. یا میتوان props را به عنوان آرگومان این متد تعریف کرد و یا میتوان توسط Object Destructuring، خواصی را که از props نیاز داریم، در پارامتر متد Input ذکر کنیم که این روش چون به نوعی اینترفیس کامپوننت را نیز مشخص میکند و همچنین کدهای تکراری دسترسی به props را به حداقل میرساند، تمیزتر و با قابلیت نگهداری بالاتری است. برای مثال هر جائیکه نام username استفاده شده بود، با خاصیت name جایگزین شده و بجای برچسب از label، بجای مقدار username از متغیر value و بجای رخداد تعریف شده نیز onChange قرار گرفتهاست.
سپس به کامپوننت فرم لاگین بازگشته و ابتدا آنرا import میکنیم:
اکنون متد رندر ماژول src\components\loginForm.jsx، به صورت زیر با درج دو Input، خلاصه میشود که دیگر در آن خبری از تگها و کدهای تکراری نیست:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-18.zip
ایجاد فرم لاگین
فرم لاگینی را که به برنامهی نمایش لیست فیلمهای تکمیل شدهی تا قسمت 17، اضافه خواهیم کرد، یک فرم بوت استرپی است و میتوانید جزئیات بیشتر مزین سازی المانهای این نوع فرمها را با کلاسهای بوت استرپ، در مطلب «کار با شیوهنامههای فرمها در بوت استرپ 4» مطالعه کنید.
در ابتدا فایل جدید src\components\loginForm.jsx را ایجاد کرده و سپس توسط میانبرهای imrc و cc در VSCode، ساختار ابتدایی کامپوننت جدید LoginForm را ایجاد میکنیم:
import React, { Component } from "react"; class LoginForm extends Component { render() { return <h1>Login</h1>; } } export default LoginForm;
import LoginForm from "./components/loginForm"; //... function App() { return ( <React.Fragment> <NavBar /> <main className="container"> <Switch> <Route path="/login" component={LoginForm} /> <Route path="/movies/:id" component={MovieForm} /> // ... </Switch> </main> </React.Fragment> ); }
<NavLink className="nav-item nav-link" to="/login"> Login </NavLink>
اکنون نوبت به افزودن فرم بوت استرپی لاگین به فایل loginForm.jsx رسیدهاست:
import React, { Component } from "react"; class LoginForm extends Component { render() { return ( <form> <div className="form-group"> <label htmlFor="username">Username</label> <input id="username" type="text" className="form-control" /> </div> <div className="form-group"> <label htmlFor="password">Password</label> <input id="password" type="password" className="form-control" /> </div> <button className="btn btn-primary">Login</button> </form> ); } } export default LoginForm;
- ابتدا المان form به صفحه اضافه میشود.
- سپس هر ورودی، داخل یک div با کلاس form-group، محصور میشود. کار آن تبدیل یک برچسب و فیلد ورودی، به یک گروه از ورودیهای بوت استرپ است.
- در اینجا هر برچسب دارای یک ویژگی for است. اما چون قرار است عبارات jsx، به معادلهای جاوا اسکریپتی ترجمه شوند، نمیتوان از واژهی کلیدی for در اینجا استفاده کرد. به همین جهت از معادل react ای آن که htmlFor است، در کدهای فوق استفاده کردهایم؛ شبیه به نکتهای که در مورد تبدیل ویژگی class به className وجود دارد. مقدار هر ویژگی htmlFor نیز به id فیلد ورودی متناظر با آن تنظیم میشود. به این ترتیب اگر کاربر بر روی این برچسب کلیک کرده و آنرا انتخاب کند، فیلد متناظر با آن، دارای focus میشود.
- فیلدهای ورودی نیز دارای کلاس form-control هستند.
با این خروجی نهایی در مرورگر:
مدیریت ارسال فرمها
به صورت پیش فرض و استاندارد، دکمهی افزوده شدهی به المان form، سبب ارسال اطلاعات آن به سرور و سپس بارگذاری کامل صفحه میشود. این رفتاری نیست که در یک برنامهی SPA مدنظر باشد. برای مدیریت این حالت، میتوان از رخداد onSubmit هر المان فرم، استفاده کرد:
class LoginForm extends Component { handleSubmit = e => { console.log("handleSubmit", e); e.preventDefault(); // call the server }; render() { return ( <form onSubmit={this.handleSubmit}> //...
دسترسی مستقیم به المانهای فرمها
پس از فراخوانی متد preventDefault، کار مدیریت ارسال فرم به سرور را باید خودمان مدیریت کنیم و دیگر رخداد full post back استاندارد به سمت سرور را نخواهیم داشت. در جاوا اسکریپت خالص برای دریافت مقادیر وارد شدهی توسط کاربر میتوان نوشت:
const username = document.getElementById("username").value;
برای دسترسی به یک المان DOM در React، باید یک reference را به آن نسبت داد. برای این منظور یک خاصیت جدید را در سطح کلاس کامپوننت، ایجاد کرده و آنرا با React.RefObject، مقدار دهی اولیه میکنیم:
class LoginForm extends Component { username = React.createRef();
<input ref={this.username} id="username" type="text" className="form-control" />
handleSubmit = e => { e.preventDefault(); // call the server const username = this.username.current.value; console.log("handleSubmit", username); };
البته در حالت کلی باید استفادهی از RefObjectها را به حداقل رساند (راه حل بهتری برای دریافت ورودیها وجود دارد) و جاهائی از آنها استفاده کرد که واقعا راه حل دیگری وجود ندارد؛ مانند تنظیم focus بر روی یک المان DOM. در این حالت حتما باید ارجاعی را از آن المان DOM در دسترس داشت و یا برای پویانمایی (animation) نیز مجبور به استفادهی از RefObjectها هستیم.
برای نمونه روش تنظیم focus بر روی یک فیلد ورودی توسط RefObjectها به صورت زیر است:class LoginForm extends Component { username = React.createRef(); componentDidMount = () => { this.username.current.focus(); };
البته روش بهتری نیز برای انجام اینکار وجود دارد. المانهای JSX دارای ویژگی autoFocus نیز هستند که دقیقا همین کار را انجام میدهد:
<input autoFocus ref={this.username} id="username" type="text" className="form-control" />
تبدیل المانهای فرمها به Controlled elements
در بسیاری از اوقات، فرمهای ما state خود را از سرور دریافت میکنند. فرض کنید که در حال ایجاد یک فرم ثبت اطلاعات فیلمها هستیم. در این حالت باید بر اساس id فیلم، اطلاعات آن را از سرور دریافت و در state ذخیره کرد؛ سپس فیلدهای فرم را بر اساس آن مقدار دهی اولیه کرد. برای نمونه در فرم لاگین میتوان state را با شیء account، به صورت زیر مقدار دهی اولیه کرد:
class LoginForm extends Component { state = { account: { username: "", password: "" } };
ابتدا ویژگی value فیلد برای مثال username را به خاصیت username شیء account موجود در state متصل میکنیم:
<input value={this.state.account.username}
<input value={this.state.account.username} onChange={this.handleChange}
handleChange = e => { const account = { ...this.state.account }; //cloning an object account.username = e.currentTarget.value; this.setState({ account }); };
مدیریت دریافت اطلاعات چندین فیلد ورودی
تا اینجا موفق شدیم اطلاعات state را به تغییرات فیلد username در فرم لاگین متصل کنیم؛ اما فیلد password را چگونه باید مدیریت کرد؟ برای اینکه تمام این مراحل را مجددا تکرار نکنیم، میتوان از مقدار دهی پویای خواص در جاوا اسکریپت که توسط [] انجام میشود استفاده کرد:
handleChange = e => { const account = { ...this.state.account }; //cloning an object account[e.currentTarget.name] = e.currentTarget.value; this.setState({ account }); };
<input id="password" name="password" value={this.state.account.password} onChange={this.handleChange} type="password" className="form-control" />
یک نکته: میتوان توسط Object Destructuring، تکرار e.currentTarget را حذف کرد:
handleChange = ({ currentTarget: input }) => { const account = { ...this.state.account }; //cloning an object account[input.name] = input.value; this.setState({ account }); };
آشنایی با خطاهای متداول دریافتی در حین کار با فرمها
فرض کنید خاصیت username را از شیء account موجود در state حذف کردهایم. در زمان نمایش ابتدایی فرم، خطایی را دریافت نخواهیم کرد، اما اگر اطلاعاتی را در آن وارد کنیم، بلافاصله در کنسول توسعه دهندگان مرورگر چنین اخطاری ظاهر میشود:
Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://fb.me/react-controlled-components
دقیقا چنین اخطاری را با ورود null/undefined بجای "" در حین مقدار دهی اولیهی username در شیء account نیز دریافت خواهیم کرد:
Warning: `value` prop on `input` should not be null. Consider using an empty string to clear the component or `undefined` for uncontrolled components.
ایجاد یک کامپوننت ورود اطلاعات با قابلیت استفادهی مجدد
هر چند در پیاده سازی فعلی سعی کردیم با بکارگیری مقداردهی پویای خواص اشیاء، تکرار کدها را کاهش دهیم، اما باز هم به ازای هر فیلد ورودی باید این مسایل تکرار شوند:
- ایجاد یک div با کلاسهای بوت استرپی.
- ایجاد label و همچنین فیلد ورودی.
- در اینجا مقدار htmlFor باید با مقدار id فیلد ورودی یکی باشد.
- مقدار دهی ویژگیهای value و onChange نیز باید تکرار شوند.
بنابراین بهتر است این تعاریف را استخراج و به یک کامپوننت با قابلیت استفادهی مجدد منتقل کرد. به همین جهت فایل جدید src\components\common\input.jsx را در پوشهی common ایجاد کرده و سپس توسط میانبرهای imrc و sfc، این کامپوننت تابعی بدون حالت را تکمیل میکنیم:
import React from "react"; const Input = ({ name, label, value, onChange }) => { return ( <div className="form-group"> <label htmlFor={name}>{label}</label> <input value={value} onChange={onChange} id={name} name={name} type="text" className="form-control" /> </div> ); }; export default Input;
سپس به کامپوننت فرم لاگین بازگشته و ابتدا آنرا import میکنیم:
import Input from "./common/input";
render() { const { account } = this.state; return ( <form onSubmit={this.handleSubmit}> <Input name="username" label="Username" value={account.username} onChange={this.handleChange} /> <Input name="password" label="Password" value={account.password} onChange={this.handleChange} /> <button className="btn btn-primary">Login</button> </form> );
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-18.zip
نظرات مطالب
لیست به روز شدهی بلاگهای IT
این لیست آخر هر هفته به روز خواهد شد و جهت دسترسی سریع لینک آن در ستون سمت راست سایت قرار گرفته است.
کسی میدونه چرا تمام tag های سایت wordpress فیلتر شده؟
یکی از خاصیتهای جالب این سایت جستجوی یک واژهی کلیدی به صورت زیر است:
http://en.wordpress.com/tag/keyword
به جای keyword عبارت مورد نظر خود را قرار دهید.
مثلا:
http://en.wordpress.com/tag/windows/
http://en.wordpress.com/tag/net/
http://en.wordpress.com/tag/windows-forms/
http://en.wordpress.com/tag/net/
http://en.wordpress.com/tag/windows-forms/
به این صورت میشود کل وبلاگهای سایت را یکجا تحت نظر داشت یا حتی روی این برچسبها فید هم تعریف میشود و در نرم افزارهای فید خوان قابل استفاده خواهد بود.
در کل بجای فیلتر کردن چند عبارت خاص نباید کل برچسبها رو یکجا فیلتر میکردند.
نظرات مطالب
اشتباهات متداول برنامهنویسهای دات نت
سلام
خبرخوان (تکست و گرافیکی)، لیست و فید وبلاگهای برنامهنویسی ارائه شده است که وبلاگ شما نیز جز آنها قرار گرفته است.
http://persianbloggers.blogspot.com/2009/03/programming-p.html
پرشین بلاگرز شما را به بازدید و استفاده از این خبرخوان و 23 خبرخوان تخصصی دیگر موجود دعوت میکند.
خبرخوان (تکست و گرافیکی)، لیست و فید وبلاگهای برنامهنویسی ارائه شده است که وبلاگ شما نیز جز آنها قرار گرفته است.
http://persianbloggers.blogspot.com/2009/03/programming-p.html
پرشین بلاگرز شما را به بازدید و استفاده از این خبرخوان و 23 خبرخوان تخصصی دیگر موجود دعوت میکند.
ثبت لینکهای مختلف در یک سیستم (مثلا قسمت به اشتراک گذاری لینکها) در ابتدای کار شاید ساده به نظر برسد؛ خوب، هر صفحهای که یک آدرس منحصربفرد بیشتر ندارد. ما هش این لینک را محاسبه میکنیم و بعد روی این هش، یک کلید منحصربفرد را تعریف خواهیم کرد تا دیگر رکوردی تکراری ثبت نشود. همچنین چون این هش نیز طول کوتاهی دارد، جستجوی آن بسیار سریع خواهد بود. واقعیت این است که خیر! این روش ناکارآمدترین حالت پردازش لینکهای مختلف است.
برای مثال لینکهای http://www.site.com و http://www.site.com/index.htm دو هش متفاوت را تولید میکنند اما در عمل یکی هستند. نمونهی دیگر، لینکهای http://www.site.com/index.htm و http://www.site.com/index.htm#section1 هستند که فقط اصطلاحا در یک fragment با هم تفاوت دارند و از این دست لینکهایی که باید در حین ثبت یکی درنظر گرفته شوند، زیاد هستند و اگر علاقمند به مرور آنها هستید، میتوانید به صفحهی URL Normalization در ویکیپدیا مراجعه کنید.
اگر نکات این صفحه را تبدیل به یک کلاس کمکی کنیم، به کلاس ذیل خواهیم رسید:
از این روش برای تمیز کردن و حذف فیدهای تکراری در فایلهای OPML تهیه شده نیز میشود استفاده کرد. عموما فیدخوانهای نهچندان با سابقه، نکات یاد شده در این مطلب را رعایت نمیکنند و به سادگی میشود در این سیستمها، فیدهای تکراری زیادی را ثبت کرد.
برای مثال اگر یک فایل OPML چنین ساختار XML ایی را داشته باشد:
هر outline آنرا به کلاس زیر میتوان نگاشت کرد:
برای اینکار فقط کافی است از LINQ to XML به نحو ذیل استفاده کنیم:
در این حالت لیست کلیه فیدهای یک گروه را چه تکراری و غیرتکراری، دریافت خواهیم کرد. برای حذف موارد تکراری نیاز است از متد Distinct استفاده شود. به همین جهت باید کلاس ذیل را نیز تدارک دید:
اکنون با کمک کلاس OpmlCompare فوق که از کلاس UrlNormalization برای تشخیص لینکهای تکراری استفاده میکند، میتوان به لیست بهتر و متعادلتری رسید:
برای مثال لینکهای http://www.site.com و http://www.site.com/index.htm دو هش متفاوت را تولید میکنند اما در عمل یکی هستند. نمونهی دیگر، لینکهای http://www.site.com/index.htm و http://www.site.com/index.htm#section1 هستند که فقط اصطلاحا در یک fragment با هم تفاوت دارند و از این دست لینکهایی که باید در حین ثبت یکی درنظر گرفته شوند، زیاد هستند و اگر علاقمند به مرور آنها هستید، میتوانید به صفحهی URL Normalization در ویکیپدیا مراجعه کنید.
اگر نکات این صفحه را تبدیل به یک کلاس کمکی کنیم، به کلاس ذیل خواهیم رسید:
using System; using System.Web; namespace OPMLCleaner { public static class UrlNormalization { public static bool AreTheSameUrls(this string url1, string url2) { url1 = url1.NormalizeUrl(); url2 = url2.NormalizeUrl(); return url1.Equals(url2); } public static bool AreTheSameUrls(this Uri uri1, Uri uri2) { var url1 = uri1.NormalizeUrl(); var url2 = uri2.NormalizeUrl(); return url1.Equals(url2); } public static string[] DefaultDirectoryIndexes = new[] { "default.asp", "default.aspx", "index.htm", "index.html", "index.php" }; public static string NormalizeUrl(this Uri uri) { var url = urlToLower(uri); url = limitProtocols(url); url = removeDefaultDirectoryIndexes(url); url = removeTheFragment(url); url = removeDuplicateSlashes(url); url = addWww(url); url = removeFeedburnerPart(url); return removeTrailingSlashAndEmptyQuery(url); } public static string NormalizeUrl(this string url) { return NormalizeUrl(new Uri(url)); } private static string removeFeedburnerPart(string url) { var idx = url.IndexOf("utm_source=", StringComparison.Ordinal); return idx == -1 ? url : url.Substring(0, idx - 1); } private static string addWww(string url) { if (new Uri(url).Host.Split('.').Length == 2 && !url.Contains("://www.")) { return url.Replace("://", "://www."); } return url; } private static string removeDuplicateSlashes(string url) { var path = new Uri(url).AbsolutePath; return path.Contains("//") ? url.Replace(path, path.Replace("//", "/")) : url; } private static string limitProtocols(string url) { return new Uri(url).Scheme == "https" ? url.Replace("https://", "http://") : url; } private static string removeTheFragment(string url) { var fragment = new Uri(url).Fragment; return string.IsNullOrWhiteSpace(fragment) ? url : url.Replace(fragment, string.Empty); } private static string urlToLower(Uri uri) { return HttpUtility.UrlDecode(uri.AbsoluteUri.ToLowerInvariant()); } private static string removeTrailingSlashAndEmptyQuery(string url) { return url .TrimEnd(new[] { '?' }) .TrimEnd(new[] { '/' }); } private static string removeDefaultDirectoryIndexes(string url) { foreach (var index in DefaultDirectoryIndexes) { if (url.EndsWith(index)) { url = url.TrimEnd(index.ToCharArray()); break; } } return url; } } }
برای مثال اگر یک فایل OPML چنین ساختار XML ایی را داشته باشد:
<?xml version="1.0" encoding="utf-8"?> <opml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" version="1.0"> <body> <outline text="آی تی ایرانی"> <outline type="rss" text="فید کلی آخرین نظرات، مطالب، اشتراکها و پروژههای .NET Tips" title="فید کلی آخرین نظرات، مطالب، اشتراکها و پروژههای .NET Tips" xmlUrl="https://www.dntips.ir/Feed/LatestChanges" htmlUrl="https://www.dntips.ir/" /> </outline> </body> </opml>
using System.Xml.Serialization; namespace OPMLCleaner { [XmlType(TypeName="outline")] public class Opml { [XmlAttribute(AttributeName="text")] public string Text { get; set; } [XmlAttribute(AttributeName = "title")] public string Title { get; set; } [XmlAttribute(AttributeName = "type")] public string Type { get; set; } [XmlAttribute(AttributeName = "xmlUrl")] public string XmlUrl { get; set; } [XmlAttribute(AttributeName = "htmlUrl")] public string HtmlUrl { get; set; } } }
var document = XDocument.Load("it-92-03-01.opml"); var results = (from node in document.Descendants("outline") where node.Attribute("htmlUrl") != null && node.Parent.Attribute("text") != null && node.Parent.Attribute("text").Value == "آی تی ایرانی" select new Opml { HtmlUrl = (string)node.Attribute("htmlUrl"), Text = (string)node.Attribute("text"), Title = (string)node.Attribute("title"), Type = (string)node.Attribute("type"), XmlUrl = (string)node.Attribute("xmlUrl") }).ToList();
using System.Collections.Generic; namespace OPMLCleaner { public class OpmlCompare : EqualityComparer<Opml> { public override bool Equals(Opml x, Opml y) { return UrlNormalization.AreTheSameUrls(x.HtmlUrl, y.HtmlUrl); } public override int GetHashCode(Opml obj) { return obj.HtmlUrl.GetHashCode(); } } }
var distinctResults = results.Distinct(new OpmlCompare()).ToList();
در مورد ELMAH پیشتر مطالبی در این سایت منتشر شده است:
از آن میتوان جهت ثبت استثناهای مدیریت نشده در انواع و اقسام برنامههای وب دات نتی استفاده کرد. از برنامههای ASP.NET Webforms ، ASP.NET MVC تا سرویسهای WCF ، WCF RIA و غیره. سپس این Http module ویژه امکان مرور خطاهای ثبت شده را از راه دور میسر میسازد، به همراه فید RSS از خطاها، امکان مشاهدهی مقادیر فیلدها در لحظهی بروز خطا، ارسال ایمیل خطاها و امکان ذخیره سازی آنها در فایلهای XML تا انواع بانکهای اطلاعاتی.
برای نمونه، سایت Stackoverflow از یک نمونهی سفارشی شدهی آن استفاده میکند (+).
همانطور که در این معرفی آمده است : ثبت استثناهای "مدیریت نشده". اما آیا امکان ثبت استثناهای مدیریت شده هم توسط آن وجود دارد؟
پاسخ: بله. به صورت زیر:
فقط کافی است ارجاعی به اسمبلی ELMAH اضافه شود و از سطر فوق استفاده گردد. به این صورت خروجی شبیه به همان خطاهای مدیریت نشده در لیست خطاهای ثبت شده توسط ELMAH ظاهر خواهد شد.
از آن میتوان جهت ثبت استثناهای مدیریت نشده در انواع و اقسام برنامههای وب دات نتی استفاده کرد. از برنامههای ASP.NET Webforms ، ASP.NET MVC تا سرویسهای WCF ، WCF RIA و غیره. سپس این Http module ویژه امکان مرور خطاهای ثبت شده را از راه دور میسر میسازد، به همراه فید RSS از خطاها، امکان مشاهدهی مقادیر فیلدها در لحظهی بروز خطا، ارسال ایمیل خطاها و امکان ذخیره سازی آنها در فایلهای XML تا انواع بانکهای اطلاعاتی.
برای نمونه، سایت Stackoverflow از یک نمونهی سفارشی شدهی آن استفاده میکند (+).
همانطور که در این معرفی آمده است : ثبت استثناهای "مدیریت نشده". اما آیا امکان ثبت استثناهای مدیریت شده هم توسط آن وجود دارد؟
پاسخ: بله. به صورت زیر:
... } catch (Exception ex) { Elmah.ErrorSignal.FromCurrentContext().Raise(ex); ...
یک نکتهی تکمیلی
در یک سری از متدهای فایل global.asax عموما Context ای وجود ندارد (دقیقا مانند برنامههای دسکتاپ). در این حالت از روش زیر
استفاده کنید:
در یک سری از متدهای فایل global.asax عموما Context ای وجود ندارد (دقیقا مانند برنامههای دسکتاپ). در این حالت از روش زیر
Elmah.ErrorLog.GetDefault(null).Log(new Error(ex))
using System; using System.Text; using Elmah; namespace Common.WebToolkit { public static class ElmahLogEx { public static void LogException(this string ex) { if (string.IsNullOrWhiteSpace(ex)) return; LogException(new Exception(ex)); } public static void LogException(this Exception ex) { if (ex == null) return; try { ErrorSignal.FromCurrentContext().Raise(ex); } catch { ErrorLog.GetDefault(null).Log(new Error(ex)); } } } }
نظرات مطالب
استفاده از Froala WYSIWYG Editor در ASP.NET
از راهنمایی شما بسیار متشکرم. دو مورد برای من پیش اومد؛ یکی اینکه با استفاده از راهنماییهای شما دکمه کد اضافه شد، اما اول اینکه زمانی روی آن کلیک میکنم هیچ اتفاقی نمیافته. با دو browser چک کردم و هیچ تغییری نکرد. مطلب دوم: در local آیکنها نمایش داده میشوند اما روی هاست آیکنها نمایش داده نمیشوند. در ابتدا از bundle استفاده میکردم اما با اینکه لینک مستقیم فایلها را هم گذاشتم باز همان مشکل را داشتم. البته فایل اصلی jquery را هم به ابتدای صفحه انتقال دادم، اما باز هم تغییری نکرد.