در قسمت قبل توضیحی کلی از ساختار پروژه را خدمت شما ارائه کردم و حالا در این قسمت باید شروع به کدنویسی کنیم. برای شروع دستور npm start را در ترمینال اجرا می کنم تا سرور توسعه محلی ما راه بیفتد. سپس در یک پنجره ترمینال دیگر دستور tsc -w را اجرا می کنم تا وارد watch mode شویم. راه های زیادی برای نوشتن این برنامه وجود دارد اما از آنجا که ما در مورد کلاس ها و غیره صحبت کردیم من پروژه را به صورت شیء گرا پیاده سازی می کنم.
ابتدا کلاسی به نام projectInput می سازم:
class ProjectInput { }
هدف ما در این کلاس این است که به Template و فرم درون آن دسترسی پیدا کرده و سپس آن را درون Div پایانی (در فایل index.html با آیدی app) نمایش بدهیم. در ابتدا constructor ای می سازم که درون آن به template و محل نمایش آن (div با آیدی app) دسترسی پیدا کنیم:
class ProjectInput { constructor() { this.templateElement = this.hostElement = } }
من این نام ها را انتخاب کرده ام، اما شما می توانید هر نام دیگری را انتخاب کنید. حالا می گوییم:
class ProjectInput { templateElement: HTMLTemplateElement; hostElement: HTMLDivElement; constructor() { this.templateElement = document.getElementById( 'project-input' )! as HTMLTemplateElement; this.hostElement = } }
همانطور که می بینید این دو خصوصیت را تعریف کرده ایم و به آن ها تایپ های مورد نظرشان را داده ایم تا بعدا به خطا برنخوریم. اگر این کار را نکنیم تایپ اسکریپت به ما خطا می دهد که چرا سعی در مقداردهی به خصوصیتی داری که اصلا در کلاس ProjectInput وجود ندارد؟ در مرحله بعد و پس از اضافه کردن این خصوصیت خارج از constructor به خطای جدیدی برخواهیم خورد که می گوید ممکن است مقدار این خصوصیت null باشد. اگر یادتان باشد در فصل های قبلی به شما گفتم که در چنین حالتی باید به تایپ اسکریپت بگوییم ما به کدهای HTML دسترسی داشته و مطمئن هستیم که مقدار درون تگ Template هیچ وقت برابر null نمی شود. این کار با استفاده از علامت ! انجام خواهد شد. بنابراین به کد زیر می رسیم:
this.templateElement = document.getElementById('project-input')!;
اما هنوز هم تایپ اسکریپت به ما خطا می دهد. چرا؟ به دلیل اینکه دستور getElementById نمی تواند تشخیص دهد که دقیقا چه عنصر HTML ای را برمی گرداند، بلکه فقط می داند نوعی عنصر HTML برگردانده خواهد شد (مثلا یک دکمه، یک فیلد، یک پاراگراف و ...). تایپ اسکریپت این مورد را خطا می داند و به ما می گوید که حتما نوع دقیق آن را برای getElementById مشخص کنیم. از آنجایی که ما مطمئن هستیم مقدار این template چه چیزی خواهد بود می توانیم از type casting استفاده کنیم. اگر از فصل های قبلی به یاد داشته باشید روش اول آن به شکل زیر بود:
this.templateElement = <HTMLTemplateElement>document.getElementById('project-input')!;
یعنی قبل از دستور با علامت های <> نوع آن را مشخص کنید. روش دوم نیز استفاده از کلیدواژه as است:
this.templateElement = document.getElementById('project-input')! as HTMLTemplateElement;
از آنجایی که روش دوم تمیزتر است من از همین روش استفاده کرده ام که در کد اولیه بالا به شما نشان داده بودم. حالا نوبت به hostElement است:
class ProjectInput { templateElement: HTMLTemplateElement; hostElement: HTMLDivElement; constructor() { this.templateElement = <HTMLTemplateElement>document.getElementById('project-input')!; this.hostElement = document.getElementById('app')! as HTMLDivElement; } }
برای مرحله بعدی باید کدی بنویسیم که محتوای درون Template را گرفته و درون DOM (تگ Div با آیدی app) نمایش بدهد. از آنجایی که می خواهم با بارگذاری برنامه حتما فرم نمایش داده شود، این کار را در constructor انجام می دهم:
class ProjectInput { templateElement: HTMLTemplateElement; hostElement: HTMLDivElement; constructor() { this.templateElement = <HTMLTemplateElement>document.getElementById('project-input')!; this.hostElement = document.getElementById('app')! as HTMLDivElement; const importedNode = document.importNode( this.templateElement.content, true ); } }
دستور importNode یک node را کپی می کند بنابراین ما محتوای (content) آن template را کپی کرده ایم و درون ثابتی به نام importedNode قرار داده ایم. آرگومان دومی که به imporNode داده ام true می باشد. اگر مانند من آرگومان دوم را true بگذارید یعنی تمام عناصر تو در تو (nested) را نیز به صورت deep کپی کند و به ما بدهد نه فقط عناصر سطحی را. در مرحله بعد باید متدی تعریف کنیم که مسئول وارد کردن عناصر ما باشد:
class ProjectInput { templateElement: HTMLTemplateElement; hostElement: HTMLDivElement; element: HTMLFormElement; 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.attach(); } private attach() { this.hostElement.insertAdjacentElement('afterbegin', this.element); } } const prjInput = new ProjectInput();
من نام این متد را attach گذاشته ام و همانطور که می بینید مقدار 'afterbegin' را به متد insertAdjacentElement داده ام که یعنی عنصر من را در همان ابتدای تگ template (تگ آغازین آن) وارد کن. ما می خواهیم importedNode را وارد کنیم اما نمی توانیم آن را به عنوان آرگومان دوم insertAdjacentElement و به صورت مستقیم پاس بدهیم. چرا؟ به دلیل اینکه اولا importedNode فقط از درون constructor قابل دسترسی است. دوما تایپ اسکریپت نوع این ثابت را DocumentFragment می شناسد و نمی توانیم چنین تایپی را مستقیما وارد این دستور کنیم. برای حل این مشکل یک خصوصیت دیگر به نام element تعریف کرده ایم تا مقدار اصلی HTML آن را بگیرد. دستور firstElementChild یعنی اولین فرزند درون importedNode (یعنی همان تگ های template) را بگیر تا مقدار واقعی HTML را داشته باشیم.
حالا می توانیم this.element را به insertAdjacentElement پاس بدهیم و متد attach را درون constructor صدا بزنیم. در نهایت یک نمونه از این کلاس را ساخته ایم تا کدهای آن اجرا شوند. حالا به محض ذخیره سازی کدها و رفتن به مرورگر، فرم را مشاهده می کنید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.