در قسمت قبل توانستیم فرم خود را در مرورگر نمایش دهیم بنابراین به خوبی جلو آمده ایم اما اگر به مرورگر خود نگاه کنید متوجه می شوید که هیچ کدام از استایل های CSS برایتان اعمال نشده اند (یکی از آن ها). اگر به فایل app.css نگاه کنید این خط از کد را می بینید:
#user-input { margin: 1rem; padding: 1rem; border: 1px solid #ff0062; background: #f7f7f7; }
اما ما هنگام ایجاد عنصر خود چنین id را برایش ثبت نکردیم بنابراین بهتر است که به constructor جلسه قبل برگشته و مثل من این خط از کد را اضافه کنید:
// بقیه کدها // const importedNode = document.importNode( this.templateElement.content, true ); this.element = importedNode.firstElementChild as HTMLFormElement; this.element.id = 'user-input'; this.attach(); // بقیه کدها //
بدین صورت می توانیم با عناصر DOM تعامل داشته باشیم. یکی دیگر از موارد تعامل من با این فرم دریافت مقادیر درون فیلدهای آن خواهد بود چرا که باید بعدا آن ها را ثبت کنیم بنابراین بهتر است به این فیلد ها دسترسی پیدا کرده و سپس یک event-listener را برای ثبت آن اضافه کنیم. من این کار را درون constructor و قبل از دستور attach انجام می دهم. در ابتدا خارج از constructor آن ها را به عنوان خصوصیت معرفی می کنیم:
class ProjectInput { templateElement: HTMLTemplateElement; hostElement: HTMLDivElement; element: HTMLFormElement; titleInputElement: HTMLInputElement; descriptionInputElement: HTMLInputElement; peopleInputElement: HTMLInputElement; // بقیه کدها //
حالا درون constructor و با استفاده از querySelector به تک تک فیلد های مورد نظر دسترسی پیدا می کنیم:
constructor() { this.templateElement = document.getElementById( 'project-input' )! as HTMLTemplateElement; this.hostElement = document.getElementById('app')! as HTMLDivElement; const importedNode = document.importNode( this.templateElement.content, true ); this.element = importedNode.firstElementChild as HTMLFormElement; this.element.id = 'user-input'; this.titleInputElement = this.element.querySelector('#title') as HTMLInputElement; this.descriptionInputElement = this.element.querySelector('#description') as HTMLInputElement; this.peopleInputElement = this.element.querySelector('#people') as HTMLInputElement; this.attach(); }
من در جلسه قبل نیز توضیح دادم که تایپ اسکریپت توانایی تشخیص این مسئله را ندارد که آیا querySelector یک عنصر input را برمی گرداند یا یک پاراگراف یا هر چیز دیگری بنابراین به ما خطا می دهد. من برای رفع این خطا از type casting استفاده کرده ام و به تایپ اسکریپت گفته ام که عنصر برگردانده شده برای titleInputElement حتما از نوع HTMLInputElement است. همین کار را با descriptionInputElement و peopleInputElement نیز انجام داده ایم. من این id ها را از فایل HTML برداشته ام بنابراین اگر فایل HTML را تغییر داده اید باید آیدی های جدید را در این قسمت قرار دهید. حالا که به input ها دسترسی داریم باید یک event-listener بسازیم تا مراقب ثبت فرم باشد. برای این کار درون کلاس یک متد private دیگر می سازیم:
private configure() { this.element.addEventListener('submit', this.submitHandler); }
this.element که همان فرم ما است که در جلسات قبل تعریف کردیم. از آنجایی که خصوصیت element را با استفاده از typecasting ایجاد کرده بودیم تایپ اسکریپت می داند که این عنصر از نوع فرم است بنابراین type completion (پیشنهاد کد در ویرایشگر) را دریافت میکنیم. همانطور که در کد بالا مشاهده می کنید در هنگام submit فرم ما یک متد به نام submitHandler اجرا خواهد شد که البته هنوز آن را تعریف نکرده ایم بنابراین این کار را نیز انجام می دهیم. یادتان باشد که ما می خواهیم مقدار وارد شده در input ها را دریافت کرده و پس از اعتبارسنجی با آن ها کاری انجام دهیم. فعلا در این جلسه می خواهیم آن ها را دریافت کنیم تا ببینیم کدهایمان کار می کنند یا خیر. به همین دلیل فقط مقادیر را console.log می کنم:
private submitHandler(event: Event) { event.preventDefault(); console.log(this.titleInputElement.value); } private configure() { this.element.addEventListener('submit', this.submitHandler); }
اگر این رویداد را preventDefault نکنیم، باعث ثبت فرم می شود که ما نمی خواهیم چنین اتفاقی بیفتد. در نهایت configure را در Constructor صدا می زنیم تا کدهایی که نوشتیم در همان ابتدای کار برنامه اجرا شوند:
// بقیه کدها // this.titleInputElement = this.element.querySelector('#title') as HTMLInputElement; this.descriptionInputElement = this.element.querySelector('#description') as HTMLInputElement; this.peopleInputElement = this.element.querySelector('#people') as HTMLInputElement; this.configure(); this.attach(); }
در حال حاضر اگر به مرورگر بروید و مقدار Hello را در title فرم وارد کنید و سپس روی دکمه Add Project کلیک کنید با خطایی در کنسول روبرو می شویم. خبر این است که فرم ثبت نمی شود و preventDefault کارش را انجام می دهد اما خبر بد این است که خطا می گوید قادر به خواندن مقدار value برای undefined نیست! چرا؟
در جلسات قبلی این دوره در رابطه با این مشکل صحبت کرده بودیم، این همان مشکل معروف this در جاوا اسکریپت است. کلیدواژه this درون متد submitHandler به کلاس اشاره نمی کند. ما درون این متد یک دستور console.log داریم:
console.log(this.titleInputElement.value);
زمانی که متدی را به رویدادی متصل می کنید (مثلا با eventlistener ها) کلیدواژه this دیگر به کلاس اشاره نخواهد کرد بلکه به هدف (target) فعلی رویداد اشاره می کند. همانطور که می دانیم راه حل استفاده از bind است:
private submitHandler(event: Event) { event.preventDefault(); console.log(this.titleInputElement.value); } private configure() { this.element.addEventListener('submit', this.submitHandler.bind(this)); }
زمانی که دستور bind را به submitHandler می چسبانیم، می خواهیم submitHandler را طوری پیکربندی کنیم تا زمان اجرا به همان شکل تعیین شده اجرا شود. من this را به bind داده ام که یعنی this درون submitHandler همان this ای خواهد بود که درون configure است که همان کلاس است. حالا دوباره به مرورگر رفته و در فیلد Title مقداری می نویسیم و Add Project را می زنیم. با این کار باید عبارت نوشته شده درون کنسول مرورگر نمایش داده شود. سوالی که از شما دارم این است که آیا راه جایگزین یا حتی بهتری برای این کار وجود دارد؟ کمک کوچکی به شما می کنم: آیا decorator ها و نوع autobind ای که نوشتیم را به یاد دارید؟
در جلسه بعد از decorator ها برای حل مشکل this استفاده می کنیم تا موارد یاد گرفته را تمرین کرده باشیم اما بدانید که این روش هم هیچ عیبی ندارد و نهایتا سلیقه شما تعیین کننده است.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.