تا این قسمت از کار عناصرِ <li> خود را draggable کرده ایم، یعنی به آن ها قابلیت drag شدن داده ایم اما هنوز نمی توانیم چیزی را drop کنیم. برای انجام این کار باید به کلاسی برویم که هدف drop است. در پروژه ما این کلاس، همان ProjectList است (یعنی یکی از دو باکس finished projects یا active projects) بنابراین می گوییم:
// ProjectList Class class ProjectList extends Component<HTMLDivElement, HTMLElement> implements DragTarget { assignedProjects: Project[];
با این کار متعهد شده ایم که از ساختار تعریف شده در interface مورد نظر (DragTarget) پیروی کنیم و سه متد آن را تعریف نماییم. با این حساب شروع به تعریف dragOverHandler می کنیم:
@autobind dragOverHandler(_: DragEvent) { const listEl = this.element.querySelector('ul')!; listEl.classList.add('droppable'); }
از آنجایی که درون این متد از this استفاده کرده ایم، autobind را به آن اضافه کرده ام تا به مشکل this برنخوریم. کاری که این متد انجام می دهد، تغییر ظاهر هدف drop است. یعنی زمانی که کاربر عنصری را drag کرده و روی یکی از دو باکس می کشد، باید ظاهر آن باکس تغییر کند تا کاربر بفهمد عنصر را در کدام قسمت رها (Drop) کند. من هم گفته ام در چنین حالتی کلاس droppable به ul ما اضافه شود تا ظاهرش تغییر کند. ما این کلاس را از قبل در فایل app.css قرار داده بودیم:
.droppable { background: #ffe3ee; }
همچنین توجه داشته باشید که پارامتر ورودی این متد را با علامت آندرلاین مشخص کرده ام چرا که فعلا نیازی به event نداریم اما بعدا برگشته و آن را تصحیح می کنیم. حالا باید مطمئن شویم که این متد در زمان مناسب اجرا می شود. برای این کار به متد configure در همین کلاس رفته و یک event-listener دیگر به آن اضافه می کنیم:
configure() { this.element.addEventListener('dragover', this.dragOverHandler); projectState.addListener((projects: Project[]) => { const relevantProjects = projects.filter(prj => { if (this.type === 'active') { return prj.status === ProjectStatus.Active; } return prj.status === ProjectStatus.Finished; }); this.assignedProjects = relevantProjects; this.renderProjects(); }); }
در حال حاضر اگر کدها را در مرورگر تست کنید، متوجه می شوید که با کشیدن li روی باکس finished projects، رنگ پس زمینه باکس به آبی روشن تغییر می کند اما اگر li را drop نکرده و همانطور موس را از روی این باکس کنار ببریم، رنگ پس زمینه به سفید برنمی گردد. اینجاست که نوبت متد dragLeaveHandler می رسد:
@autobind dragOverHandler(_: DragEvent) { const listEl = this.element.querySelector('ul')!; listEl.classList.add('droppable'); } dropHandler(_: DragEvent) {} @autobind dragLeaveHandler(_: DragEvent) { const listEl = this.element.querySelector('ul')!; listEl.classList.remove('droppable'); }
من فعلا متد dropHandler را خالی گذاشته ام تا بعدا به آن برگردیم. برای نوشتن dragLeaveHandler تنها کار ما حذف کردن کلاس droppable است و نیازی به انجام کار خاصی نداریم. در نهایت event-listener های این متد و متد dropHandler را نیز می نویسم:
configure() { this.element.addEventListener('dragover', this.dragOverHandler); this.element.addEventListener('dragleave', this.dragLeaveHandler); this.element.addEventListener('drop', this.dropHandler); projectState.addListener((projects: Project[]) => { const relevantProjects = projects.filter(prj => { if (this.type === 'active') { return prj.status === ProjectStatus.Active; } return prj.status === ProjectStatus.Finished; }); this.assignedProjects = relevantProjects; this.renderProjects(); }); }
در حال حاضر ما می توانیم از نظر ظاهری عناصر خود را drag و drop کنیم اما برای جاوا اسکریپت هیچ کدام از این مسائل مشخص نیست. جاوا اسکرپیت انسان نیست بنابراین نمی فهمد که چه اتفاقی در حال رخ دادن است. برای حل این مشکل ابتدا به کلاس ProjectItem برمی گردیم و در متد dragStartHandler به شکل زیر کدنویسی کنیم:
@autobind dragStartHandler(event: DragEvent) { event.dataTransfer!.setData('text/plain', this.project.id); event.dataTransfer!.effectAllowed = 'move'; }
توجه داشته باشید که event های drag خصوصیتی به نام dataTransfer دارند که با استفاده از آن می توانیم داده هایی را به drag خود متصل کنیم و در هنگام drop آن داده ها را استخراج کنیم. باز هم باید با علامت ! به تایپ اسکریپت بگوییم که از وجود dataTransfer روی این event مطمئن هستیم (اگر event ما از نوع drag نباشد، خصوصیت dataTransfer نیز موجود نخواهد بود یا مثلا بسته به نوع listener و موارد دیگر ممکن است dataTransfer ما null باشد). برای ذخیره داده ها روی drag از متد SetData استفاده می کنیم که دو پارامتر ورودی می گیرد: اولین پارامتر نوع داده ما را مشخص می کند که ما text/plain را انتخاب کرده ایم، یعنی ما فقط می خواهیم مقادیر متنی را به drag بچسبانیم (اگر با Drag & drop آشنایی ندارید می توانید به صفحه توسعه دهندگان موزیلا مراجعه کنید). پارامتر دوم نیز خود داده هایی را مشخص می کند که قرار است به Drag چسبانده شوند (در اینجا id مورد نظر کافی است).
سپس با استفاده از effectAllowed مشخص کرده ایم که هدف ما move کردن (جا به جایی عنصر) است تا cursor مرورگر به شکل مورد نظر در بیاید و مرورگر هم بفهمد که هدف ما از این عملیات Drag & drop چیست. حالا به کلاس ProjectList می رویم و در dragOverHandler کدهای خود را می نویسیم:
@autobind dragOverHandler(event: DragEvent) { if (event.dataTransfer && event.dataTransfer.types[0] === 'text/plain') { event.preventDefault(); const listEl = this.element.querySelector('ul')!; listEl.classList.add('droppable'); } }
در پروژه ما فقط یک drag & drop داریم اما در پروژه های بزرگ تر ممکن است چندین دسته drag & drop داشته باشیم و نخواهیم هر عنصری که drag شده است در هر قسمتی از برنامه Drop شود. به همین دلیل باید چک کنیم که آیا drag شدن در این قسمت مجاز است یا خیر. ابتدا می گوییم آیا خصوصیت datatTransfer وجود دارد و آیا ایندکس اول از خصوصیت types درون dataTransfer از نوع text/plain است یا خیر. با این کار جلوی drop شدن عناصر دیگر (مثلا تصاویر و ...) را در این قسمت می گیریم.
همچنین باید بدانید که رفتار پیش فرض جاوا اسکریپت برای drop این است که به عنصری اجازه drop ندهد بنابراین حتما باید از preventDefault استفاده کنیم تا جلوی این رفتار جاوا اسکریپت را بگیریم. اگر preventDefault را صدا نزنیم، کدهای dropHandler هیچ وقت اجرا نخواهند شد. حالا نوبت به dropHandler می رسد که در قسمت بعد انجام می دهیم.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.