پروژه Drag & Drop: نهایی کردن قابلیت Drag & Drop

Drag & Drop Project: Finalizing the Drag & Drop Feature

20 مرداد 1399
پروژه ی Drag & Drop: نهایی کردن قابلیت Drag & Drop

در قسمت قبل با موفقیت قابلیت drag و همچنین drop را روی پروژه خود پیاده کردیم اما هنوز کدی برای پیاده سازی منطق پروژه ننوشته ایم. یعنی در حال حاضر drag و drop کردن پروژه ها فقط ظاهری است و در عمل هیچ اتفاقی در برنامه ما نمی افتد. بیایید در قدم اول به متد dropHandler برگردیم و کد ساده ای را برای تست کردن آن بنویسیم:

  @autobind
  dropHandler(event: DragEvent) {
    console.log(event.dataTransfer!.getData('text/plain'));
  }

اگر یادتان باشد در جلسه قبل گفتیم که باید اطلاعات خاصی را به drag بچسبانیم تا هنگام drop آن ها را استخراج کنیم. اطلاعاتی که ما به drag خود چسباندیم فقط id پروژه مورد نظر بود:

  @autobind
  dragStartHandler(event: DragEvent) {
    event.dataTransfer!.setData('text/plain', this.project.id);
    event.dataTransfer!.effectAllowed = 'move';
  }

بر همین اساس در متد dropHandler این اطلاعات را بازیابی کرده ایم. متد GetData برای خواندن اطلاعات موجود در dataTransfer به کار می رود. در صورتی که دوست دارید خصوصیات و متدهای بیشتری از dataTransfer ببینید، آن را به صورت خالی در کنسول مرورگر log کنید (مثلا event.dataTrasnfer را console.log کنید).

با این کار اگر به مرورگر بروید و یک پروژه را Drag کرده و در قسمت دیگری drop کنید، id مربوط به آن در کنسول مرورگر نمایش داده می شود. حالا که مطمئن شدیم کدهای ما کار می کند باید id پاس داده شده را ذخیره کنیم (log کردن فقط جهت تست بود):

  @autobind
  dropHandler(event: DragEvent) {
    const prjId = event.dataTransfer!.getData('text/plain');
  }

همچنین اگر از قسمت قبل به یاد داشته باشید هدف ما move کردن بود یعنی انتقال یک پروژه از یک دسته بندی به دسته بندی دیگر (از خصوصیت effectAllowed برای این کار استفاده کردیم). بر همین اساس باید پروژه ما از یک دسته به دسته دیگر انتقال پیدا کند. از آنجایی که پروژه های ما درون state برنامه قرار دارند می توانیم به کلاس ProjectState رفته و متدی به نام moveProject ایجاد کنیم:

// بقیه کدها //
  addProject(title: string, description: string, numOfPeople: number) {
    const newProject = new Project(
      Math.random().toString(),
      title,
      description,
      numOfPeople,
      ProjectStatus.Active
    );
    this.projects.push(newProject);
    this.updateListeners();
  }

  moveProject(projectId: string, newStatus: ProjectStatus) {
    const project = this.projects.find(prj => prj.id === projectId);
    if (project && project.status !== newStatus) {
      project.status = newStatus;
      this.updateListeners();
    }
  }

ما دو عامل مهم را در این انتقال می بینیم: پروژه و محل جا به جایی آن. احتمالا تصور کنید که برای محل جا به جایی باید همیشه finished را به active و active را به finished تغییر دهیم اما این کار اشتباه است. چرا؟ به دلیل اینکه شاید کاربر ما پروژه ای را drag کرده و درون همان کادر قبلی drop کند. در این صورت وضعیت پروژه تغییری نکرده است بلکه کاربر از تغییر وضعیت پروژه منصرف شده است. با این توصیفات اگر متدها فقط وضعیت پروژه را بدون در نظر گرفتن رفتار کاربر تغییر دهد، یک باگ بزرگ را به برنامه معرفی کرده ایم.

بر اساس این توضیحات این متد دو پارامتر ورودی می گیرد: اولین پارامتر id پروژه و دومین پارامتر وضعیت جدید پروژه است (active یا finished). در قدم اول با استفاده از متد find در بین آرایه پروژه ها (projects) گردش می کنیم تا پروژه مورد نظر را بر اساس id دریافتی پیدا کنیم. در مرحله بعد در یک شرط ساده دو مورد را چک کرده ایم: اولا project وجود داشته باشد (ممکن است null باشد) و دوما وضعیت فعلی پروژه (project.status) برابر با وضعیت جدید نباشد. اگر این دو شرط برقرار بودند می توانیم وضعیت پروژه را روی وضعیت جدید تنظیم کنیم و در نهایت متدی به نام updateListeners را صدا می زنیم که در ادامه دلیل آن را توضیح می دهم. توجه داشته باشید که اگر وضعیت فعلی پروژه برابر با وضعیت جدید پروژه باشد یعنی کاربر آیتم را در همان باکس قبلی Drop کرده است بنابراین نیازی به تغییر وضعیت نیست، دلیل شرط ما این است.

از آنجایی که چیزی در پروژه ما تغییر کرده است تمام listener ها باید دوباره render شوند تا متوجه تغییرات جدید بشوند. بنابراین کدهای این کلاس به شکل زیر در می آیند:

class ProjectState extends State<Project> {
  private projects: Project[] = [];
  private static instance: ProjectState;

  private constructor() {
    super();
  }

  static getInstance() {
    if (this.instance) {
      return this.instance;
    }
    this.instance = new ProjectState();
    return this.instance;
  }

  addProject(title: string, description: string, numOfPeople: number) {
    const newProject = new Project(
      Math.random().toString(),
      title,
      description,
      numOfPeople,
      ProjectStatus.Active
    );
    this.projects.push(newProject);
    this.updateListeners();
  }

  moveProject(projectId: string, newStatus: ProjectStatus) {
    const project = this.projects.find(prj => prj.id === projectId);
    if (project && project.status !== newStatus) {
      project.status = newStatus;
      this.updateListeners();
    }
  }

  private updateListeners() {
    for (const listenerFn of this.listeners) {
      listenerFn(this.projects.slice());
    }
  }
}

یعنی متدی به نام  updateListeners تعریف می کنیم و کار گردش بین listener ها را به آن می دهیم (اگر یادتان باشد این کار قبلا بر عهده addProject بود). سپس این متد را هم درون moveProject و هم درون addProject صدا می زنیم. حالا می توانیم درون dropHandler از این کدها استفاده کنیم. قبل از آن باید یادآور شوم که پروژه های ما خصوصیتی به نام type داشتند که همان وضعیت پروژه را مشخص می کرد (درون کلاس ProjectList):

// ProjectList Class
class ProjectList extends Component<HTMLDivElement, HTMLElement>
  implements DragTarget {
  assignedProjects: Project[];

  constructor(private type: 'active' | 'finished') {
    super('project-list', 'app', false, `${type}-projects`);
    this.assignedProjects = [];

    this.configure();
    this.renderContent();
  }
// بقیه کدها //

همانطور که می بینید تایپ یا active است یا finished بنابراین به متد dropHandler می رویم و می گوییم:

// بقیه کدها //
  @autobind
  dropHandler(event: DragEvent) {
    const prjId = event.dataTransfer!.getData('text/plain');
    projectState.moveProject(
      prjId,
      this.type === 'active' ? ProjectStatus.Active : ProjectStatus.Finished
    );
  }

پارامتر اول که id پروژه بود که آن را پاس داده ایم. پارامتر دوم وضعیت جدید پروژه است. من برای پاس دادن این پارامتر یک شرط ساده نوشته ام که اگر type پروژه ما active بود مقدار projectStatus.Active پاس داده شود (از enum ما) و در غیر این صورت ProjectStatus.Finished پاس داده خواهد شد. به همین سادگی پروژه drag & drop خود را کامل کرده ایم.

من سورس کد این پروژه را برای شما قرار می دهم تا آن را با کدهای خودتان مقایسه کنید. امیدوارم این پروژه به شما قدرت خوبی برای درک تایپ اسکریپت داده باشد. در فصل بعد در رابطه با namespace ها و module ها صحبت خواهیم کرد.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش جامع تایپ اسکریپت توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

ما را دنبال کنید
اینستاگرام روکسو تلگرام روکسو ایمیل و خبرنامه روکسو