File

projects/ngx-property-grid/src/lib/property-grid.component.ts

Implements

AfterContentInit AfterViewInit

Metadata

selector ngx-property-grid
styles .property-grid { /*border: solid 1px #95B8E7;*/ } .property-grid-border { border: 1px solid #d6d6d678 } .property-grid-table { border-spacing: 0; padding: 5px } .property-grid-group { background-color: white; font-weight: bold; color: #616161; padding-top: 8px; padding-bottom: 5px; } .property-grid-label, .property-grid-control { border: dotted 1px #ccc; padding: 2px 5px; } .internal-property-grid { margin-top: 12px; } .internal-property-grid .property-grid { border-width: 0; } .internal-property-grid .property-grid-header { margin-bottom: 5px; background-color: #f5f5f5; padding-bottom: 5px; padding-top: 5px; padding-left: 5px; box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1); -webkit-box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1); width: 100%; } .internal-property-grid .property-grid-header-margin { margin-left: 5px; margin-right: 5px; width: unset; } .internal-property-grid .property-grid-table { border-width: 0; /*border-top: 1px solid #dbdbdb;*/ } .card { background-color: #fff; box-shadow: 0 6px 10px 0 rgba(0, 0, 0, .14), 0 1px 18px 0 rgba(0, 0, 0, .12), 0 3px 5px -1px rgba(0, 0, 0, .2); display: flex; flex-flow: row wrap; /*margin: 5px 20px;*/ padding: 0; } .internal-property-grid ngx-property-grid .card { background-color: unset; box-shadow: unset; display: unset; flex-flow: unset; /*margin: 5px 20px;*/ padding: unset; }
template
<div class="property-grid" [ngClass]="!isInternal && !cardStyle ? 'property-grid-border': null" [style.width]="width">
  <div [ngClass]="cardStyle ? 'card' : null">
    <table class="property-grid-table" [style.width]="width">
      <tbody>
      <ng-container *ngFor="let group of groups">
        <tr *ngIf="group.name">
          <td colspan="2" class="property-grid-group" (click)="groupCollapse && group.toggle()">{{group.name}}</td>
        </tr>

        <ng-container *ngFor="let item of group.items">
          <ng-container *ngIf="!hidden(item)">
            <tr *ngIf="group.state">
              <td [attr.colspan]="item.colSpan2 == true ? 2 : 1"
                  class="property-grid-label"
                  [style.cursor]="item.link ? 'pointer' : null"
                  (click)="openLink(item.link)">
                {{item.name}}
                <span *ngIf="showHelp && item.showHelp && item.description" [title]="item.description">[?]</span>
              </td>
              <ng-container *ngIf="!item.colSpan2">
                <ng-container
                  *ngTemplateOutlet="controlTemplate; context: {$implicit: item}">
                </ng-container>
              </ng-container>
            </tr>
            <tr *ngIf="group.state && item.colSpan2">
              <ng-container *ngTemplateOutlet="controlTemplate; context: {$implicit: item}"></ng-container>
            </tr>
          </ng-container>
        </ng-container>
      </ng-container>
      </tbody>
    </table>
  </div>

  <div *ngFor="let item of subItems" class="internal-property-grid" [ngClass]="cardStyle ? 'card' : null">
    <ng-container *ngIf="!hidden(item)">

      <div (click)="pg.toggle()" class="property-grid-header"
           [ngClass]="cardStyle ? null : 'property-grid-header-margin'">
        <b>{{item.name}}</b>
      </div>
      <ngx-property-grid
        [showHelp]="showHelp"
        [collapse]="item.collapse"
        [@collapseAnimation]="pg.collapse ? 'hidden' : 'visible' "
        [options]="options[item.key]"
        [width]="width"
        [labelWidth]="labelWidth"
        [templateMap]="templateMap"
        style="display: block;overflow: hidden"
        #pg>
      </ngx-property-grid>
    </ng-container>
  </div>
</div>


<ng-template #controlTemplate let-item>
  <td [ngSwitch]="controlType(item)" [attr.colspan]="$any(item).colSpan2 == true ? 2 : 1" class="property-grid-control">
    <ng-container *ngSwitchCase="'template'">
      <ng-container *ngTemplateOutlet="getTemplate($any(item).type); context: {$implicit: propertyValue(item)}">
      </ng-container>
    </ng-container>

    <ng-container
      *ngSwitchCase="'dynamicComponent'"
      [dynamicComponentLoad]="item"
      [options]="options">
    </ng-container>

    <span *ngSwitchCase="'templateNotFound'">
      {{item.type}} template Not Found
    </span>
  </td>
</ng-template>


<ng-container *ngIf="!isInternal">

  <ng-template ngxTemplate="checkbox" let-p>
    <input type="checkbox" [(ngModel)]="$any(p).value"/>
  </ng-template>

  <ng-template ngxTemplate="color" let-p>
    <input type="color" [(ngModel)]="$any(p).value"/>
  </ng-template>

  <ng-template ngxTemplate="date" let-p>
    <input type="date" [(ngModel)]="$any(p).value"/>
  </ng-template>

  <ng-template ngxTemplate="label" let-p>
    <label>{{$any(p).value}}</label>
  </ng-template>

  <ng-template ngxTemplate="text" let-p>
    <input type="text" [(ngModel)]="$any(p).value"/>
  </ng-template>

  <ng-template ngxTemplate="options" let-p>
    <select [(ngModel)]="$any(p).value">
      <option [value]="optionValue(option)" *ngFor="let option of $any(p).options">
        {{optionLabel(option)}}
      </option>
    </select>
  </ng-template>
</ng-container>

Index

Properties
Methods
Inputs
Accessors

Constructor

constructor(el: ElementRef, cdr: ChangeDetectorRef)
Parameters :
Name Type Optional
el ElementRef<HTMLElement> No
cdr ChangeDetectorRef No

Inputs

cardStyle
Default value : true
collapse
Default value : true
groupCollapse
Default value : false
labelWidth
Type : string | number
Default value : '120px'
meta
Type : any
options
Type : any
showHelp
Default value : true
templateMap
Type : literal type
width
Type : string | number

Methods

Public controlType
controlType(meta: PropertyItemMeta)
Parameters :
Name Type Optional
meta PropertyItemMeta No
Returns : "template" | "dynamicComponent" | "templateNotFound"
Public getTemplate
getTemplate(type: string)
Parameters :
Name Type Optional
type string No
Returns : TemplateRef<any>
Public hidden
hidden(meta: PropertyItemMeta)
Parameters :
Name Type Optional
meta PropertyItemMeta No
Returns : boolean
Private initMeta
initMeta()
Returns : void
ngAfterContentInit
ngAfterContentInit()
Returns : void
ngAfterViewInit
ngAfterViewInit()
Returns : void
Public openLink
openLink(link: string)
Parameters :
Name Type Optional
link string No
Returns : void
optionLabel
optionLabel(v: any)
Parameters :
Name Type Optional
v any No
Returns : string
optionValue
optionValue(v: any)
Parameters :
Name Type Optional
v any No
Returns : any
Public propertyValue
propertyValue(meta: PropertyItemMeta)
Parameters :
Name Type Optional
meta PropertyItemMeta No
Returns : PropertyValue
Public toggle
toggle()
Returns : void

Properties

Private _meta
Type : any
Private _options
Type : any
Private _templateLoaded
Default value : false
defaultTemplates
Type : QueryList<NgxTemplate>
Decorators :
@ViewChildren(NgxTemplate)
Public groups
Type : InternalGroup[]
Public Readonly isInternal
Type : boolean
Default value : false
Public subItems
Type : PropertyItemMeta[]
templates
Type : QueryList<NgxTemplate>
Decorators :
@ContentChildren(NgxTemplate)

Accessors

templateLoaded
gettemplateLoaded()
meta
getmeta()
setmeta(v: any)
Parameters :
Name Type Optional
v any No
Returns : void
options
getoptions()
setoptions(v: any)
Parameters :
Name Type Optional
v any No
Returns : void
import {
  AfterContentInit, AfterViewInit, ChangeDetectorRef,
  Component,
  ContentChildren, ElementRef,
  Input,
  QueryList,
  TemplateRef, Type, ViewChildren
} from '@angular/core';
import {animate, state, style, transition, trigger} from '@angular/animations';
import {NgxTemplate} from 'ngx-template';
import {PropertyItemMeta} from './property-item-meta';
import {PropertyValue} from './property-value';

@Component({
  selector: 'ngx-property-grid',
  template: `
    <div class="property-grid" [ngClass]="!isInternal && !cardStyle ? 'property-grid-border': null" [style.width]="width">
      <div [ngClass]="cardStyle ? 'card' : null">
        <table class="property-grid-table" [style.width]="width">
          <tbody>
          <ng-container *ngFor="let group of groups">
            <tr *ngIf="group.name">
              <td colspan="2" class="property-grid-group" (click)="groupCollapse && group.toggle()">{{group.name}}</td>
            </tr>

            <ng-container *ngFor="let item of group.items">
              <ng-container *ngIf="!hidden(item)">
                <tr *ngIf="group.state">
                  <td [attr.colspan]="item.colSpan2 == true ? 2 : 1"
                      class="property-grid-label"
                      [style.cursor]="item.link ? 'pointer' : null"
                      (click)="openLink(item.link)">
                    {{item.name}}
                    <span *ngIf="showHelp && item.showHelp && item.description" [title]="item.description">[?]</span>
                  </td>
                  <ng-container *ngIf="!item.colSpan2">
                    <ng-container
                      *ngTemplateOutlet="controlTemplate; context: {$implicit: item}">
                    </ng-container>
                  </ng-container>
                </tr>
                <tr *ngIf="group.state && item.colSpan2">
                  <ng-container *ngTemplateOutlet="controlTemplate; context: {$implicit: item}"></ng-container>
                </tr>
              </ng-container>
            </ng-container>
          </ng-container>
          </tbody>
        </table>
      </div>

      <div *ngFor="let item of subItems" class="internal-property-grid" [ngClass]="cardStyle ? 'card' : null">
        <ng-container *ngIf="!hidden(item)">

          <div (click)="pg.toggle()" class="property-grid-header"
               [ngClass]="cardStyle ? null : 'property-grid-header-margin'">
            <b>{{item.name}}</b>
          </div>
          <ngx-property-grid
            [showHelp]="showHelp"
            [collapse]="item.collapse"
            [@collapseAnimation]="pg.collapse ? 'hidden' : 'visible' "
            [options]="options[item.key]"
            [width]="width"
            [labelWidth]="labelWidth"
            [templateMap]="templateMap"
            style="display: block;overflow: hidden"
            #pg>
          </ngx-property-grid>
        </ng-container>
      </div>
    </div>


    <ng-template #controlTemplate let-item>
      <td [ngSwitch]="controlType(item)" [attr.colspan]="$any(item).colSpan2 == true ? 2 : 1" class="property-grid-control">
        <ng-container *ngSwitchCase="'template'">
          <ng-container *ngTemplateOutlet="getTemplate($any(item).type); context: {$implicit: propertyValue(item)}">
          </ng-container>
        </ng-container>

        <ng-container
          *ngSwitchCase="'dynamicComponent'"
          [dynamicComponentLoad]="item"
          [options]="options">
        </ng-container>

        <span *ngSwitchCase="'templateNotFound'">
          {{item.type}} template Not Found
        </span>
      </td>
    </ng-template>


    <ng-container *ngIf="!isInternal">

      <ng-template ngxTemplate="checkbox" let-p>
        <input type="checkbox" [(ngModel)]="$any(p).value"/>
      </ng-template>

      <ng-template ngxTemplate="color" let-p>
        <input type="color" [(ngModel)]="$any(p).value"/>
      </ng-template>

      <ng-template ngxTemplate="date" let-p>
        <input type="date" [(ngModel)]="$any(p).value"/>
      </ng-template>

      <ng-template ngxTemplate="label" let-p>
        <label>{{$any(p).value}}</label>
      </ng-template>

      <ng-template ngxTemplate="text" let-p>
        <input type="text" [(ngModel)]="$any(p).value"/>
      </ng-template>

      <ng-template ngxTemplate="options" let-p>
        <select [(ngModel)]="$any(p).value">
          <option [value]="optionValue(option)" *ngFor="let option of $any(p).options">
            {{optionLabel(option)}}
          </option>
        </select>
      </ng-template>
    </ng-container>
  `,
  styles: [
      `
      .property-grid {
        /*border: solid 1px #95B8E7;*/
      }

      .property-grid-border {
        border: 1px solid #d6d6d678
      }

      .property-grid-table {
        border-spacing: 0;
        padding: 5px
      }

      .property-grid-group {
        background-color: white;
        font-weight: bold;
        color: #616161;
        padding-top: 8px;
        padding-bottom: 5px;
      }

      .property-grid-label, .property-grid-control {
        border: dotted 1px #ccc;
        padding: 2px 5px;
      }

      .internal-property-grid {
        margin-top: 12px;
      }

      .internal-property-grid .property-grid {
        border-width: 0;
      }

      .internal-property-grid .property-grid-header {
        margin-bottom: 5px;
        background-color: #f5f5f5;
        padding-bottom: 5px;
        padding-top: 5px;
        padding-left: 5px;
        box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
        -webkit-box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
        width: 100%;
      }

      .internal-property-grid .property-grid-header-margin {
        margin-left: 5px;
        margin-right: 5px;
        width: unset;
      }

      .internal-property-grid .property-grid-table {
        border-width: 0;
        /*border-top: 1px solid #dbdbdb;*/
      }

      .card {
        background-color: #fff;
        box-shadow: 0 6px 10px 0 rgba(0, 0, 0, .14), 0 1px 18px 0 rgba(0, 0, 0, .12), 0 3px 5px -1px rgba(0, 0, 0, .2);
        display: flex;
        flex-flow: row wrap;
        /*margin: 5px 20px;*/
        padding: 0;
      }

      .internal-property-grid ngx-property-grid .card {
        background-color: unset;
        box-shadow: unset;
        display: unset;
        flex-flow: unset;
        /*margin: 5px 20px;*/
        padding: unset;
      }
    `
  ],
  animations: [
    trigger('collapseAnimation', [
      state('hidden', style({
        height: '0',
        // overflow: 'hidden',
      })),
      state('visible', style({
        height: '*'
      })),
      transition('visible <=> hidden', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
    ]),
    trigger('flyInOut', [
      state('in', style({transform: 'translateX(0)'})),
      transition('void => *', [
        style({transform: 'translateX(-100%)'}),
        animate(100)
      ]),
      transition('* => void', [
        animate(100, style({transform: 'translateX(100%)'}))
      ])
    ])
  ]
})
export class PropertyGridComponent implements AfterContentInit, AfterViewInit {
  private _options: any;
  private _meta: any;
  private _templateLoaded = false;
  public get templateLoaded(): boolean {
    return this._templateLoaded;
  }

  public readonly isInternal: boolean = false;

  @Input()
  public templateMap: { [key: string]: TemplateRef<any> };

  @Input()
  public collapse = true;

  @Input()
  width: string | number;


  @Input()
  labelWidth: string | number = '120px';

  @Input()
  cardStyle = true;

  @Input()
  groupCollapse = false;

  @Input()
  showHelp = true;

  @Input()
  public set meta(v: any) {
    this._meta = v;
    this.initMeta();
  }

  public get meta(): any {
    return this._meta;
  }

  @Input()
  public set options(v: any) {
    this._options = v;
    if (v.__meta__) {
      this.meta = v.__meta__;
    }
  }

  public get options(): any {
    return this._options;
  }

  @ViewChildren(NgxTemplate) defaultTemplates: QueryList<NgxTemplate>;
  @ContentChildren(NgxTemplate) templates: QueryList<NgxTemplate>;

  public groups: InternalGroup[];
  public subItems: PropertyItemMeta[];

  constructor(el: ElementRef<HTMLElement>, private cdr: ChangeDetectorRef) {
    this.isInternal = el.nativeElement.parentElement && el.nativeElement.parentElement.classList &&
      el.nativeElement.parentElement.classList.contains('internal-property-grid');
  }

  ngAfterViewInit(): void {
    if (this.isInternal) {
      this._templateLoaded = true;
    } else {
      if (this.defaultTemplates) {
        this.defaultTemplates.forEach((item) => {
          if (!this.templateMap.hasOwnProperty(item.name)) {
            this.templateMap[item.name] = item.template;
          }
        });
        this._templateLoaded = true;
        this.cdr.detectChanges();
      }
    }
  }

  ngAfterContentInit(): void {
    if (!this.isInternal) {
      if (!this.templateMap) {
        this.templateMap = {};
      }
      this.templates.forEach((item) => {
        this.templateMap[item.name] = item.template;
      });
    }
  }

  public openLink(link: string) {
    if (link) {
      window.open(link, '_blank');
    }
  }

  public getTemplate(type: string): TemplateRef<any> {
    if (typeof type === 'string' && this.templateMap) {
      return type ? this.templateMap[type] : this.templateMap.default;
    } else {
      return undefined;
    }
  }

  public controlType(meta: PropertyItemMeta): 'template' | 'dynamicComponent' | 'templateNotFound' {
    if (meta.type instanceof Type) {
      return 'dynamicComponent';
    }
    if (this.getTemplate(meta.type)) {
      return 'template';
    }
    return 'templateNotFound';
  }

  public hidden(meta: PropertyItemMeta): boolean {
    if (typeof meta.hidden === 'boolean') {
      return meta.hidden;
    }
    if (typeof meta.hidden === 'function') {
      return meta.hidden(this._options);
    }
    return false;
  }

  public propertyValue(meta: PropertyItemMeta): PropertyValue {
    return new PropertyValue(this.options, meta);
  }

  public toggle(): void {
    this.collapse = !this.collapse;
  }

  private initMeta(): void {
    const meta: object = this.meta;
    if (!meta) {
      this.subItems = [];
      return;
    }

    const groups: InternalGroup[] = [new InternalGroup(undefined)];
    const subItems: PropertyItemMeta[] = [];
    for (const i in meta) {
      if (!meta.hasOwnProperty(i)) {
        continue;
      }
      const v: PropertyItemMeta = meta[i];
      if (v.type === 'subItems') {
        subItems.push(v);
        continue;
      }

      let group = groups.find(o => o.name === v.group);
      if (!group) {
        group = new InternalGroup(v.group);
        groups.push(group);
      }
      group.items.push(v);
    }
    groups.forEach(o => o.items.sort((a, b) => a.order - b.order));

    this.groups = groups.filter(o => o.items.length > 0);
    this.subItems = subItems;
  }

  optionLabel(v: any): string {
    if (typeof v === 'string') {
      return v;
    }
    if (v.text) {
      return v.text;
    }
    if (v.label) {
      return v.label;
    }
    return v;
  }

  optionValue(v: any): any {
    return v && v.value ? v.value : v;
  }
}

export class InternalGroup {
  public readonly items: PropertyItemMeta[] = [];
  public type = 'group';

  public state = true;

  public toggle(): void {
    this.state = !this.state;
  }

  constructor(public name: string) {
  }
}

      .property-grid {
        /*border: solid 1px #95B8E7;*/
      }

      .property-grid-border {
        border: 1px solid #d6d6d678
      }

      .property-grid-table {
        border-spacing: 0;
        padding: 5px
      }

      .property-grid-group {
        background-color: white;
        font-weight: bold;
        color: #616161;
        padding-top: 8px;
        padding-bottom: 5px;
      }

      .property-grid-label, .property-grid-control {
        border: dotted 1px #ccc;
        padding: 2px 5px;
      }

      .internal-property-grid {
        margin-top: 12px;
      }

      .internal-property-grid .property-grid {
        border-width: 0;
      }

      .internal-property-grid .property-grid-header {
        margin-bottom: 5px;
        background-color: #f5f5f5;
        padding-bottom: 5px;
        padding-top: 5px;
        padding-left: 5px;
        box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
        -webkit-box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
        width: 100%;
      }

      .internal-property-grid .property-grid-header-margin {
        margin-left: 5px;
        margin-right: 5px;
        width: unset;
      }

      .internal-property-grid .property-grid-table {
        border-width: 0;
        /*border-top: 1px solid #dbdbdb;*/
      }

      .card {
        background-color: #fff;
        box-shadow: 0 6px 10px 0 rgba(0, 0, 0, .14), 0 1px 18px 0 rgba(0, 0, 0, .12), 0 3px 5px -1px rgba(0, 0, 0, .2);
        display: flex;
        flex-flow: row wrap;
        /*margin: 5px 20px;*/
        padding: 0;
      }

      .internal-property-grid ngx-property-grid .card {
        background-color: unset;
        box-shadow: unset;
        display: unset;
        flex-flow: unset;
        /*margin: 5px 20px;*/
        padding: unset;
      }
    
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""