برنامه نویسی

Angular: MatPaginator Custom Styling – DEV Community

به‌عنوان یک توسعه‌دهنده فرانت‌اند، احتمالاً وظیفه‌ای برای نمایش داده‌ها در یک جدول داشته‌اید و به‌عنوان یک توسعه‌دهنده فرانت‌اند، احتمالاً Angular Material را برای این کار انتخاب کرده‌اید.

در مقاله قبلی خود Angular: Infinite Scrolling اشاره کردم که چگونه می توانید جدول بی نهایت را با چند عنصر اولیه شروع کنید، اما علیرغم اینکه یک راه حل UX خوب است، گاهی اوقات مجبوریم صفحه بندی سنتی را انجام دهیم.

هنگامی که MatPaginator را جستجو می کنید، راه حل خود را پیدا می کنید، با این حال، یک نکته قابل توجه است… صفحه بندی Angular اصلا جذاب به نظر نمی رسد. آیا خوب نیست اگر بتوانیم ناوبری آن را حفظ کنیم، اما جلوه های بصری آن را به چیز دیگری تغییر دهیم؟

هدف

در مقاله زیر به صورت گام به گام توضیح خواهیم داد که چگونه می توانیم یک دستورالعمل سفارشی را پیاده سازی کنیم و آن را به آن پیوست کنیم mat-paginator که به طور کامل رابط کاربری خود را به مثال زیر تغییر می دهد.

کل کد منبع در StackBlitz موجود است.

توضیحات تصویر

ما یک دستورالعمل به نام ایجاد خواهیم کرد appBubblePagination، که از قدرت استفاده می کند Renderer2 برای ایجاد عناصر UI سفارشی جایگزین طرح صفحه‌بندی پیش‌فرض، و استفاده از دستورالعمل به صورت زیر خواهد بود:

<table mat-table [dataSource]="dataSource" [trackBy]="identity">
        <!-- table content -->
</table>

<mat-paginator
  appBubblePagination
  [appCustomLength]="dataSource.data.length"
  [length]="dataSource.data.length"
  [pageSize]="20"
>
</mat-paginator>
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مرحله 1.) یک دستورالعمل ایجاد کنید

برای دستیابی به هدف تغییر جنبه بصری صفحه بندی مت، دستورالعملی ایجاد کرده و وابستگی های زیر را وارد می کنیم.

@Directive({
  selector: '[appBubblePagination]',
  standalone: true,
})
export class BubblePaginationDirective {
  constructor(
    @Host() @Self() @Optional() private readonly matPag: MatPaginator,
    private elementRef: ElementRef,
    private ren: Renderer2
  ) {}
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

وابستگی ها به صورت زیر استفاده می شوند:

  • MatPaginator – ارجاع به پیوست است mat-paginator. برای تغییر دستی فهرست صفحه بندی با کلیک بر روی حباب های سفارشی استفاده می شود.
  • ElementRef – ارجاع به عناصر DOM است (mat-paginator) که بخشنامه به آن پیوست شده است. از آن برای به دست آوردن مرجعی که در آن عناصر HTML اضافی ارائه می شود استفاده می شود. در مورد Angular docs بیشتر بخوانید.
  • Renderer2 – امکان رندر کردن عناصر HTML، افزودن/حذف کلاس های css و پیوست کردن شنوندگان (کلیک کنید، شناور کنید). در مورد Angular docs بیشتر بخوانید.

مرحله 2.) طرح بندی پیش فرض را تغییر دهید

بعد، می خواهیم کمی جنبه بصری صفحه بندی پیش فرض را تغییر دهیم. در زیر تصویری از حالت شروع و پایان وجود دارد.

توضیحات تصویر

تغییرات شامل:

  • حذف متن “اقلام در صفحه”.
  • شماره صفحه بندی فعلی (1-20) را در سمت راست قرار دهید و تصاویر آن را اصلاح کنید

با وابستگی ها ElementRef و Renderer2، ما طرح را به صورت زیر تغییر می دهیم:

export class BubblePaginationDirective implements AfterViewInit {

  ngAfterViewInit(): void {
    this.styleDefaultPagination();
  }

  private styleDefaultPagination() {
    const nativeElement = this.elementRef.nativeElement;

    const itemsPerPage = nativeElement.querySelector(
      '.mat-mdc-paginator-page-size'
    );
    const howManyDisplayedEl = nativeElement.querySelector(
      '.mat-mdc-paginator-range-label'
    );

    // remove 'items per page'
    this.ren.setStyle(itemsPerPage, 'display', 'none');

    // style text of how many elements are currently displayed
    this.ren.setStyle(howManyDisplayedEl, 'position', 'absolute');
    this.ren.setStyle(howManyDisplayedEl, 'left', '0');
    this.ren.setStyle(howManyDisplayedEl, 'color', '#919191');
    this.ren.setStyle(howManyDisplayedEl, 'font-size', '14px');
  }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مرحله 3. یک DIV بین دکمه ناوبری وارد کنید

با بررسی عنصر HTML می بینیم که یک وجود دارد mat-mdc-paginator-range-actions کلاس متصل به یک عنصر div wrapper. ما می خواهیم این div را هدف قرار دهیم و یک دیوی جدید درج کنیم div عنصر بین دکمه های فلش چپ و راست. از آن به عنوان مکانی برای تولید دکمه های صفحه بندی حباب سفارشی استفاده می شود.

توضیحات تصویر

ngAfterViewInit(): void {
    this.styleDefaultPagination();
    this.createBubbleDivRef();
  }

private createBubbleDivRef(): void {
    const actionContainer = this.elementRef.nativeElement.querySelector(
      'div.mat-mdc-paginator-range-actions'
    );
    const nextButtonDefault = this.elementRef.nativeElement.querySelector(
      'button.mat-mdc-paginator-navigation-next'
    );

    // create a HTML element where all bubbles will be rendered
    this.bubbleContainerRef = this.ren.createElement('div') as HTMLElement;
    this.ren.addClass(this.bubbleContainerRef, 'g-bubble-container');

    // render element before the 'next button' is displayed
    this.ren.insertBefore(
      actionContainer,
      this.bubbleContainerRef,
      nextButtonDefault
    );
  }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما ارجاع به عنصر div wrapper را توسط actionContainer، با این حال ما نیز به آن نیاز داریم nextButtonDefault به منظور اتصال صحیح ما div عنصر در DOM، جایی که دکمه ها ارائه می شوند. ما همچنین این مرجع را در آن ذخیره می کنیم bubbleContainerRef.

با استفاده از this.ren.insertBefore() را وصل می کنیم div عنصر داخل mat-mdc-paginator-range-actions کلاس، قرار دادن div عنصر قبل از دکمه بعدی صفحه بندی.

اگر از ما استفاده می شد this.ren.appendChild(actionContainer, this.bubbleContainerRef); نتیجه نهایی دکمه های بعد از پیمایش پیکان ارائه می شود.

توضیحات تصویر

مرحله 4. رندر دکمه ها به DOM

در مرحله چهارم می خواهیم دکمه اول و آخر را رندر کنیم و بین آنها و بقیه دکمه ها نقطه اضافه کنیم. حتی اگر همه چیز را به این مثال ارائه کنیم، آنچه را که در ابتدا هر عنصر تنظیم شده است، مشاهده خواهید کرد display: none، که در مرحله بعدی فقط موارد ضروری را آشکار می کنیم.

توضیحات تصویر

export class BubblePaginationDirective implements AfterViewInit {
  /**
   * how many elements are in the table
   */
  @Input() appCustomLength: number = 0;

  ngAfterViewInit(): void {
    this.styleDefaultPagination();
    this.createBubbleDivRef();
    this.buildButtons();
  }

    // .... previous code

  /**
   * end result: (1) .... (4) (5) (6) ... (25)
   */
  private buildButtons(): void {
    const neededButtons = Math.ceil(
      this.appCustomLength / this.matPag.pageSize
    );

    // if there is only one page, do not render buttons
    if (neededButtons === 1) {
      this.ren.setStyle(this.elementRef.nativeElement, 'display', 'none');
      return;
    }

    // create first button
    this.buttonsRef = [this.createButton(0)];

    // add dots (....) to UI
    this.dotsStartRef = this.createDotsElement();

    // create all buttons needed for navigation (except the first & last one)
    for (let index = 1; index < neededButtons - 1; index++) {
      this.buttonsRef = [...this.buttonsRef, this.createButton(index)];
    }

    // add dots (....) to UI
    this.dotsEndRef = this.createDotsElement();

    // create last button to UI after the dots (....)
    this.buttonsRef = [
      ...this.buttonsRef,
      this.createButton(neededButtons - 1),
    ];
  }

  /**
   * create button HTML element
   */
  private createButton(i: number): HTMLElement {
    const bubbleButton = this.ren.createElement('div');
    const text = this.ren.createText(String(i + 1));

    // add class & text
    this.ren.addClass(bubbleButton, 'g-bubble');
    this.ren.setStyle(bubbleButton, 'margin-right', '8px');
    this.ren.appendChild(bubbleButton, text);

    // react on click
    this.ren.listen(bubbleButton, 'click', () => {
      this.switchPage(i);
    });

    // render on UI
    this.ren.appendChild(this.bubbleContainerRef, bubbleButton);

    // set style to hidden by default
    this.ren.setStyle(bubbleButton, 'display', 'none');

    return bubbleButton;
  }

  /**
   * Helper function to switch page
   */
  private switchPage(i: number): void {
    const previousPageIndex = this.matPag.pageIndex;
    this.matPag.pageIndex = i;
    this.matPag['_emitPageEvent'](previousPageIndex);
  }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بیایید مرور کنیم که چه اتفاقی در حال رخ دادن است.

تابع buildButtons():

  • محاسبه تعداد دکمه های صفحه بندی برای رندر کردن توسط neededButtons چون مجموع عناصر جدول است (appCustomLength) تقسیم بر اندازه صفحه بندی.
  • اگر عناصر موجود در جدول کمتر از اندازه صفحه بندی باشند، چیزی را ارائه نمی کنیم
  • اگر موارد بیشتری در جدول وجود دارد (neededButtons > 1) ما:
    • اولین دکمه را رندر کنید
    • رندر نقطه ها
    • دکمه های باقیمانده به جز آخرین مورد را رندر کنید
    • رندر نقطه ها
    • آخرین دکمه را رندر کنید
  • دکمه ها را ذخیره کنید createButton() به buttonsRef آرایه، زیرا بعداً مورد نیاز خواهند بود

تابع createButton():

  • یک شاخص دریافت می کند، آن را یک افزایش می دهد و این مقادیر را به عنوان یک مقدار متنی به دکمه با چند کلاس CSS اضافی اضافه می کند.
  • هر دکمه از DOM مخفی است، تنظیم شده توسط this.ren.setStyle(bubbleButton, 'display', 'none');. در مرحله بعد، برخی از آنها را در حین حرکت کاربر با کلیک روی آنها نمایش/پنهان می کنیم.
  • با پیوست کردن شنونده رویداد کلیک روی هر دکمه this.ren.listen(bubbleButton, 'click' ...)، تابع helper را می نامیم switchPage() برای تغییر صفحه فعلی

از مثال بالا createDotsElement() وجود ندارد، زیرا یک تابع رندر مشابه است createButton() و همچنین تعدادی کلاس CSS مانند g-bubble برای یک ظاهر طراحی شده

یادداشت جانبی: در switchPage() من لزوما مطمئن نیستم که چرا با _emitPageEvent نیاز به انتشار دارد previousPageIndex. من نتوانستم پاسخی برای آن پیدا کنم، اما کار می کند.

مرحله 5. به کلیک های ناوبری کاربر گوش دهید

به عنوان آخرین مرحله، می‌خواهیم منطقی را پیاده‌سازی کنیم که به کلیک کاربر روی دکمه‌های حباب گوش می‌دهد و شاخص فعال را با یک استایل تغییر می‌دهد.

در این لحظه همه دکمه ها مخفی هستند و مرجع آنها در آن نگه داشته می شود buttonsRef. ما می خواهیم فقط 2-3 دکمه اول را نمایش دهیم و همانطور که کاربر به هر جهت حرکت می کند، می خواهیم 2 دکمه اضافی به راست و چپ و همچنین اگر کاربر در وسط پیمایش باشد، نقاط بین اولین و آخرین دکمه را نمایش دهیم.

export class BubblePaginationDirective implements AfterViewInit {

    private buttonsRef: HTMLElement[] = [];

    // .... previous code

    ngAfterViewInit(): void {
    this.styleDefaultPagination();
    this.createBubbleDivRef();
    this.buildButtons();

    this.matPag.page
    .pipe(
      map((e) => [e.previousPageIndex ?? 0, e.pageIndex]),
      startWith([0, 0])
    )
    .subscribe(([prev, curr]) => {
      this.changeActiveButtonStyles(prev, curr);
    });
  }

    // .... previous code

    private changeActiveButtonStyles(previousIndex: number, newIndex: number) {
    const previouslyActive = this.buttonsRef[previousIndex];
    const currentActive = this.buttonsRef[newIndex];

    // remove active style from previously active button
    this.ren.removeClass(previouslyActive, 'g-bubble__active');

    // add active style to new active button
    this.ren.addClass(currentActive, 'g-bubble__active');

    // hide all buttons
    this.buttonsRef.forEach((button) =>
      this.ren.setStyle(button, 'display', 'none')
    );

    // show 2 previous buttons and 2 next buttons
    const renderElements = 2;
    const endDots = newIndex < this.buttonsRef.length - renderElements - 1;
    const startDots = newIndex - renderElements > 0;

    const firstButton = this.buttonsRef[0];
    const lastButton = this.buttonsRef[this.buttonsRef.length - 1];

    // last bubble and dots
    this.ren.setStyle(this.dotsEndRef, 'display', endDots ? 'block' : 'none');
    this.ren.setStyle(lastButton, 'display', endDots ? 'flex' : 'none');

    // first bubble and dots
    this.ren.setStyle(
      this.dotsStartRef,
      'display',
      startDots ? 'block' : 'none'
    );
    this.ren.setStyle(firstButton, 'display', startDots ? 'flex' : 'none');

        // resolve starting and ending index to show buttons
    const startingIndex = startDots ? newIndex - renderElements : 0;
    const endingIndex = endDots
      ? newIndex + renderElements
      : this.buttonsRef.length - 1;

    // display starting buttons
    for (let i = startingIndex; i <= endingIndex; i++) {
      const button = this.buttonsRef[i];
      this.ren.setStyle(button, 'display', 'flex');
    }
  }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ترسناک به نظر میرسه درسته 😓 نیازی به نگرانی نیست بیایید هر مرحله فکر کنیم که در این عملکرد چه اتفاقی می افتد، به شما کمک می کند تا این مزخرفات را درک کنید تا بتوانید بعداً آن را تغییر دهید.

ابتدا می خواهیم مشترک شویم [this.matPag.page](http://this.matPag.page) قابل مشاهده و دریافت نمایه صفحه بندی کلیک شده قبلی و جدید. قابل مشاهده با هر کلیک دکمه حباب فعال می شود، زیرا در مرحله قبل ما پیوست می کنیم switchPage() عملکرد به هر حباب

که در changeActiveButtonStyles() موارد زیر اتفاق می افتد:

  • ما عوض می کنیم g-bubble__active از حباب فعال قبلی به حباب جدید، که دکمه فعال فعلی را با نمایه های جدید ارائه شده برای عملکرد برجسته می کند.
  • به طور پیش فرض همه دکمه ها را پنهان کنید (display: none)
  • تعیین کنید که آیا نقطه های پایانی و آخرین دکمه نشان داده شود endDots، اگر newIndex آخرین 2 دکمه نیست
  • تعیین کنید که آیا نقطه های شروع را در ابتدا با اولین دکمه نشان دهید یا خیر startDots، اگر newIndex بیش از 2
  • محاسبه شاخص برای نمایش دکمه ها از buttonsRef، برای نمایش 2 دکمه قبلی (startingIndex) و 2 دکمه بعدی (endingIndex)
  • مقدار نمایش را برای دکمه هایی که باید قابل مشاهده باشند تغییر دهید

خلاصه

این پست وبلاگ نحوه تغییر استایل مولفه MatPaginator Angular Material را برای ایجاد یک UI صفحه بندی سفارشی توضیح می دهد. این پست از طریق ایجاد یک دستورالعمل سفارشی، اصلاح طرح پیش‌فرض، قرار دادن یک div بین دکمه‌های ناوبری، رندر کردن دکمه‌ها به DOM و گوش دادن به کلیک‌های ناوبری کاربر می‌گذرد.

نتیجه نهایی یک رابط کاربری صفحه‌بندی سفارشی با دکمه‌های حباب‌دار و نقطه‌هایی بین اولین و آخرین دکمه است. کل کد منبع این مثال در StackBlitz موجود است.

امیدوارم پست را دوست داشته باشید، به آن دل ببندید و با من در ارتباط باشید:
dev.to | لینکدین | وب سایت شخصی | Github

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا