创建类似于primeng table的自定义表格

问题描述 投票:0回答:2

在我们之前的项目中,我们使用了 primeng 的 p-table。现在我们尝试创建一个自定义的。

所以,我想创建一个类似于https://www.primefaces.org/primeng/#/table

的自定义表格

我需要一个带有复选框的表格,我不需要排序、分页、过滤和其他功能。

我搜索创建自定义表格,大多数都无法自定义我们要显示的列。 我的意思是,自定义表格组件已设计为静态或从 api 响应绑定。

即自定义组件看起来像这样。 (我不喜欢这个)

在app.component.ts中

   <custom-table [options]="tableOptions">Custom table here</custom-table>

自定义表.ts

<table class="table-striped table-hover custom-table">
  <thead>
    <tr>
      <th class="th-checkbox">
        <tri-state-checkbox class="toggle-all" [items]="filteredDataObservable"></tri-state-checkbox>
      </th>
      <th *ngFor="let column of options.columns" (click)="sortHeaderClick(column.value)" [ngClass]="{ 'sorting': isSorting(column.value), 'sorting_asc': isSortAsc(column.value), 'sorting_desc': isSortDesc(column.value) }">
        <span [innerHTML]="column.name"></span>
      </th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let row of filteredData">
      <td class="td-checkbox">
        <input type="checkbox" [(ngModel)]="row.isSelected">
      </td>
      <td *ngFor="let column of options.columns">{{getCellValue(row, column)}}</td>
    </tr>
  </tbody>
</table>

我需要如下所示。我们可以自定义 tbody,tbody 与我们要显示的内容

在app.component.ts中

<custom-table
    #table
      [value]="holidays"
      [(selection)]="selectedItems"
    >
      <ng-template pTemplate="colgroup" let-columns>
        <colgroup>
          <col style="width: 100px" />
          <col style="width: 150px" />
          <col style="width: 100px" />
          <col style="width: 120px" />
        </colgroup>
      </ng-template>
      <ng-template pTemplate="header">
        <tr>
          <th >
            <tableHeaderCheckbox></tableHeaderCheckbox>
          </th>
          <th pSortableColumn="id">
            Id <p-sortIcon field="id"></p-sortIcon>
          </th>
          <th pSortableColumn="title">
            Title <p-sortIcon field="title"></p-sortIcon>
          </th>
          <th pSortableColumn="slug">
            Slug <p-sortIcon field="slug"></p-sortIcon>
          </th>
        </tr>
      </ng-template>
      <ng-template pTemplate="body" let-data>
        <tr>
          <td>
            <tableCheckbox [value]="data"></tableCheckbox>
          </td>
          <td>{{ data.id }}</td>
          <td>{{ data.title }}</td>
          <td>{{ data.slug }}</td>
        </tr>
      </ng-template>
    </custom-table>

我想创建一个自定义TableComponent来像这样绑定。

请帮助我如何做到这一点。有例子吗?

angular primeng angular9 angular-datatables
2个回答
1
投票

您可以使用 Angular 中的 TemplateRef 机制来做到这一点。您可以使用 @ContentChildren 访问模板。

或者您可以使用指令来访问模板。

首先,您需要一个 Provider 来在模板和表之间进行通信(也适用于其他组件)

import { InjectionToken, TemplateRef } from "@angular/core";

export interface TemplateConsumer{
    setTemplate(name: string, template: TemplateRef<unknown>);
}

export const APP_TEMPLATE_CONSUMER_ACCESSOR =
    new InjectionToken<ReadonlyArray<TemplateConsumer>>('TemplateConsumerAccessor');

然后您应该创建一个指令来注入接口(来自父提供程序)并调用 setTemplate 函数。

import { Directive, Inject, Input, TemplateRef } from "@angular/core";
import { APP_TEMPLATE_CONSUMER_ACCESSOR, TemplateConsumer } from "./models/TemplateConsumer";

@Directive({
    selector: '[appTemplate]'
})
export class TemplateDirective {

    constructor(
        // Injecting  the component instance that provides APP_TEMPLATE_CONSUMER_ACCESSOR
        @Inject(APP_TEMPLATE_CONSUMER_ACCESSOR) private readonly templateConsumer: TemplateConsumer,
        private readonly templateRef: TemplateRef<unknown>
    ) { }

    private _appTemplate: string;

    @Input()
    set appTemplate(value: string) {
        this._appTemplate = value;
        this.templateConsumer.setTemplate(value, this.templateRef);
    }

    get appTemplate(): string {
        return this._appTemplate;
    }

}

您应该实现 TemplateConsumer 接口并在表组件中为其提供 APP_TEMPLATE_CONSUMER_ACCESSOR 令牌。

import { Component, forwardRef, Input, TemplateRef } from '@angular/core';
import { APP_TEMPLATE_CONSUMER_ACCESSOR, TemplateConsumer } from '../models/TemplateConsumer';

@Component({
  selector: 'app-my-alternate-list',
  template: `
      <div class="my-list">
        <ng-container *ngIf="!!data && data.length > 0 else emptyTemplate">
        <div class="list-item" *ngFor="let item of data">
        <ng-container *ngTemplateOutlet="itemTemplate;context:{item:item}"></ng-container>
      </div>
      </ng-container>
    </div>
    `,
    // Providing the component instance as TemplateConsumer
  providers: [{
    provide: APP_TEMPLATE_CONSUMER_ACCESSOR,
    useExisting: forwardRef(() => MyAlternateListComponent)
  }]

})
export class MyAlternateListComponent implements TemplateConsumer {


  @Input()
  data: Array<unknown>;

  itemTemplate: TemplateRef<unknown>;

  emptyTemplate: TemplateRef<unknown>;

  private readonly templateNameSetters = {
    item: (templateRef: TemplateRef<unknown>) => {
      this.itemTemplate = templateRef;
    },
    empty: (templateRef: TemplateRef<unknown>) => {
      this.emptyTemplate = templateRef;
    }
  };

  setTemplate(name: string, template: TemplateRef<unknown>) {
    if (!!this.templateNameSetters[name]) {
      this.templateNameSetters[name](template);
    } else {
      // Optional, Throwing an error can cause usability difficulties in some cases.
      throw new Error(`'${name}' template is not supported by '${MyAlternateListComponent.name}'`);
    }
  }
}

例如,我更喜欢基本列表,但您也可以将其用于表格。 有关 Angular 模板以及 primeng 如何使用这些模板的更多信息,您可以查看我在 Medium 上的帖子。 https://medium.com/javascript-in-plain-english/an-alternative-way-to-use-angular-templates-97becc84e109


0
投票
    <div class="container-fluid mt-5">
      <div class="table-responsive shadow">
        <table class="table  table-bordereless mb-0">
          <thead class="table-head">
            <tr>
              @for (head of thead; track head.displayName) {
              <th [class]="head.thClass" [ngClass]="{ 'pointer': head?.sortable }" [style]="head.thStyle">
                @switch (head.elementType)
                {
                <!-- #region Text -->
                @case ('text') {
                <span [id]="head?.id" (click)="head?.event == 'click'? eventTriggered($event, head.displayName): '' "
                  [class]="head?.class" [style]="head?.style">
                  @if (head?.sortable) {
                  <i class="fa-solid fa-sort"></i>
                  }
                  {{head.displayName}}
                </span>
                }
                <!-- #endregion -->
    
                <!-- #region innerHTML -->
                @case ('innerHTML') {
                <span (click)="head?.event === 'click' ? eventTriggered($event,head.displayName) : ''"
                  [innerHTML]="head.displayName" [id]="head?.id" [class]="head?.class" [style]="head?.style">
                </span>
                }
                <!-- #endregion -->
    
                <!-- #region input -->
                @case ('input') {
                <input [type]="head?.inputType" [id]="head?.id" [class]="head?.class" [style]="head?.style"
                  (click)="head?.event === 'click' ? eventTriggered($event,head.displayName,'click') : ''"
                  (change)="head?.event === 'change' ? eventTriggered($event,head.displayName,'change') : ''">
                <label [for]="head?.id" [class]="head?.class" [innerHTML]="head?.inputLabel"></label>
                }
                <!-- #endregion -->
    
                <!-- #region icon -->
                @case ('icon') {
                <button class="btn border-0" [id]="head?.id" [style]="head?.style"
                  (click)="head.event === 'click' ? eventTriggered($event,head.displayName):''">
                  <i [class]="head?.iconClass"></i>
                </button>
                }
                @case ('iconWithText') {
                <button class="btn border-0" [id]="head?.id" [style]="head?.style"
                  (click)="head.event === 'click' ? eventTriggered($event,head.displayName):''">
                  <i [class]="head?.iconClass"></i>
                </button>
                <span>{{head.iconText}}</span>
                }
                <!-- #endregion -->
                }
              </th>
              }
            </tr>
          </thead>
          <tbody>
            @for (data of dataArr | paginate:{itemsPerPage: pagination.pageSize , currentPage: pagination.page , totalItems:
            pagination.totalItems}; track data?.id;) {
            <tr>
              @for (body of tbody; track $index; ) {
              <td [style]="body?.tdStyle" [class]="body.tdClass"
                [ngClass]="{'pointer':body?.routerLink || body?.clickFunction}"
                (click)="(body?.clickFunction && body.parameter) ? eventTriggered($event,data[body.parameter],'click') : ''"
                [routerLink]="body?.routerLink">
    
                @switch (body.elementType) {
    
                <!-- #region Text -->
                @case ('text') {
                <span [class]="body?.class" [style]="body?.style" [id]="body?.id" 
                [pTooltip]="data[body.attrName]?.length >=40 ? data[body.attrName] : ''" tooltipPosition="top">
                  {{data[body.attrName]?.slice(0,40)}}
                </span>
                @if (data[body.attrName]?.length >= 40) {
                  <span>...</span>
                }
                }
                <!-- #endregion -->
    
                <!-- #region Serial No -->
                @case ('serialNo') {
                <span [class]="body?.class" [style]="body?.style" [id]="body?.id">
                  {{($index + 1 ) }}
    
                </span>
                }
                <!-- #endregion -->
    
                <!-- #region input -->
                @case ('input') {
                <input [type]="body?.inputType" [class]="body?.inputClass"
                  [id]="body.inputId ? 'id-'+data[body.inputId] : 'id'+body.attrName"
                  (click)="body?.event === 'click' ? eventTriggered($event,(body.parameter ? data[body.parameter]:data[body.attrName]),body.action ? body.action : 'click') : ''"
                  (change)="body?.event === 'change' ? eventTriggered($event,(body.parameter ? data[body.parameter] : data[body.attrName]),body.action ? body.action : 'change'):''">
                @if (body.inputLabel) {
                <label [for]="body.inputId ? body.inputId : 'id'+body.attrName">{{body.inputLabel}}</label>
                }
                }
                <!-- #endregion -->
    
                <!-- #region innerHTML -->
                @case ('innerHTML') {
                <span [innerHTML]="body.attrName ? data[body.attrName] : body?.innerHTML" [class]="body?.class"
                  [style]="body?.style" [id]="body?.id"
                  (click)="body?.event === 'click' ? eventTriggered($event,(body.parameter ? data[body.parameter] : data[body.attrName])) : ''">
                </span>
                }
                <!-- #endregion -->
    
                <!-- #region Dropdown -->
                @case ('dropdown') {
                <div class="dropdown">
                  <button class="btn p-0 px-3 border-0" data-bs-toggle="dropdown">
                    <i class="fa-solid fa-ellipsis-vertical"></i>
                  </button>
                  <ul class="dropdown-menu dropdown-overflow">
                    @for (dd of body?.dropdownData; track dd.content) {
                    <li>
                      <span class="dropdown-item pointer" [attr.data-bs-toggle]="dd.modelId ? 'modal' : ''"
                        [attr.data-bs-target]="dd.modelId ? '#'+dd.modelId : ''"
                        [innerHTML]="(dd.icon ? iconArr[dd['icon']] : '') +'&nbsp;&nbsp;'+dd?.content"
                        (click)="eventTriggered($event,data[dd.parameter],dd?.icon+(body?.attrName ? '-' +body.attrName : ''))"></span>
                    </li>
                    }
                  </ul>
                </div>
                }
                <!-- #endregion -->
    
                <!-- #region Icon -->
                @case ('icon') {
                <button class="btn border-0" [id]="body?.id" [style]="body?.style"
                  [attr.data-bs-toggle]="body.modelId ? 'modal' : ''"
                  [attr.data-bs-targrt]="body.modelId ? '#'+body.modelId : ''"
                  (click)="body?.event === 'click' ? eventTriggered($event,body.parameter && data[body.parameter],'click '+body.attrName) : ''"
                  (dblclick)="body?.event == 'dblclick' ? eventTriggered($event,body.parameter && data[body.parameter],'dblclick'+body.attrName) : ''">
                  <i [class]="body?.iconClass"></i>
                </button>
                }
                @case ('iconWithText') {
                <button class="btn border-0" [id]="body?.id" [style]="body?.style"
                  [attr.data-bs-toggle]="body.modelId ? 'modal' : ''"
                  [attr.data-bs-targrt]="body.modelId ? '#'+body.modelId : ''"
                  (click)="body?.event === 'click' ? eventTriggered($event,body.parameter && data[body.parameter],'click'+body.attrName) : ''"
                  (dblclick)="body?.event == 'dblclick' ? eventTriggered($event,body.parameter && data[body.parameter],'dblclick'+body.attrName) : ''">
                  <i [class]="body?.iconClass"></i>
                </button>
                <span>{{body?.iconText}}</span>
                }
                <!-- #endregion -->
    
                <!-- #region Select -->
                @case ('select') {
                <select [class]="body?.class" [style]="body?.style"
                  [id]="body?.id+'_'+(body.parameter ? data[body.parameter] : '')"
                  (change)="eventTriggered($event,body.parameter ? data[body.parameter] : '','change')">
                  @for (option of body?.optionArr; track option) {
                  <option [value]="body.optionValue ? option[body.optionValue] : option"
                    [selected]="body.selecterOption && body.optionValue ? data[body.selecterOption] == option[body.optionValue] : false  ">
                    {{body.optionLabel ? option[body.optionLabel] : option}}
                  </option>
                  }
                </select>
                }
                <!-- #endregion -->
    
                <!-- #region Conditional -->
                @case ('conditional') {
                <span [class]="body?.class" [style]="body?.style">
                  @if (data[body.attrName] == body.condition) {
                  <span [innerHTML]="body?.trueStatement"></span>
                  }@else {
                  <span [innerHTML]="body?.falseStatement"></span>
                  }
                </span>
                }
                <!-- #endregion -->
                }
              </td>
              }
            </tr>
            }
          </tbody>
        </table>
      </div>
      <div class="table-bottom">
        <div class="row align-items-center mt-3 px-3">
          <div class="col-lg-3">
            <span>
              &nbsp;Displaying
              {{ ( ( pagination.page - 1 ) * ( pagination.pageSize ) ) + 1 }}
              to
              {{ ( ( pagination.page - 1 ) * ( pagination.pageSize ) + (pagination.pageSize) >
              pagination.totalItems )
              ? pagination.totalItems
              : ( pagination.page - 1 ) * ( pagination.pageSize ) + (pagination.pageSize) }}
              of {{ pagination.totalItems }}
            </span>
          </div>
          <div class="col-lg-9 text-end">
            <pagination-controls (pageChange)="changePage($event)" previousLabel="" nextLabel="">
            </pagination-controls>
          </div>
        </div>
      </div>
    </div>

   <!-- #region typescript -->

type Thead = {
  displayName: string;
  sortable?: boolean;
  sortItem?: string;
  thClass: string | '';
  thStyle?: string;
  inputClass?: string;
  inputId?: string;
  iconClass?: string;
  class?: string;
  id?: string;
  style?: string;
  // for elements and events
  elementType: Exclude<typeElement, 'serialNo' | 'conditional' | 'dropdown'>;
  iconText?: string;
  inputType?: string;
  inputLabel?: string;
  event?: typeEvent;
  action?: string;
};

type Tbody = {
  attrName: string;
  tdClass: string | '';
  tdStyle?: string;
  inputClass?: string;
  inputId?: string;
  iconClass?: string;
  class?: string;
  id?: string;
  style?: string;
  innerHTML?: string;

  //for elements and events
  elementType: typeElement;
  iconText?: string;
  inputType?: string;
  inputLabel?: string;
  event?: typeEvent;
  modelId?: string;

  //for routers and parameters
  routerLink?: string;
  clickFunction?: string;
  parameter?: string;
  action?: string;

  //for dropdown
  dropdownData?: typeDropdown[];

  //for conditional
  condition?: unknown;
  trueStatement?: unknown;
  falseStatement?: unknown;

  //for select
  optionArr?: any[];
  optionValue?: string;
  optionLabel?: string;
  selecterOption?: string;
};

type typeElement =
  | 'input'
  | 'icon'
  | 'text'
  | 'serialNo'
  | 'iconWithText'
  | 'innerHTML'
  | 'dropdown'
  | 'conditional'
  | 'select';

type typeEvent = 'click' | 'change' | 'input' | 'dblclick';
type TriggeredEvent = { event: Event; action?: string; parameter: string };
type typeDropdown = {
  icon?: string;
  content: string;
  parameter: string;
  modelId?: string;
};

type Pagination = {
  totalItems: number;
  page: number;
  pageSize: number;
  optimization: boolean;
  getPagination?: boolean;
};

type PaginationOutput = {
  page: number;
  pageSize: number;
};

export { Thead, Tbody, TriggeredEvent, Pagination, PaginationOutput };

this is a custom table i created how but the $index serial number is not working how do i fix it
© www.soinside.com 2019 - 2024. All rights reserved.