Skip to content

Commit

Permalink
feat(primeng/p-badge): rework dynamic property rerender for directive
Browse files Browse the repository at this point in the history
  • Loading branch information
volvachev committed Apr 18, 2024
1 parent f41fa9c commit 7b19c33
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 109 deletions.
195 changes: 109 additions & 86 deletions src/app/components/badge/badge.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CommonModule, DOCUMENT } from '@angular/common';
import { AfterViewInit, ChangeDetectionStrategy, Component, Directive, ElementRef, Inject, Input, NgModule, OnDestroy, Renderer2, ViewEncapsulation, booleanAttribute } from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, Component, Directive, ElementRef, Inject, Input, NgModule, Renderer2, OnChanges, SimpleChanges, ViewEncapsulation, booleanAttribute } from '@angular/core';
import { SharedModule } from 'primeng/api';
import { DomHandler } from 'primeng/dom';
import { UniqueComponentId } from 'primeng/utils';
Expand All @@ -13,117 +13,95 @@ import { UniqueComponentId } from 'primeng/utils';
class: 'p-element'
}
})
export class BadgeDirective implements AfterViewInit, OnDestroy {
/**
* Icon position of the component.
* @group Props
*/
@Input() iconPos: 'left' | 'right' | 'top' | 'bottom' = 'left';
export class BadgeDirective implements OnChanges, AfterViewInit {
/**
* When specified, disables the component.
* @group Props
*/
@Input('badgeDisabled') get disabled(): boolean {
return this._disabled;
}
set disabled(val: boolean) {
this._disabled = val;
}
@Input('badgeDisabled') public disabled: boolean;
/**
* Size of the badge, valid options are "large" and "xlarge".
* @group Props
*/
@Input() public get size(): 'large' | 'xlarge' {
return this._size;
}
set size(val: 'large' | 'xlarge') {
this._size = val;

if (this.initialized) {
this.setSizeClasses();
}
}
@Input() public badgeSize: 'large' | 'xlarge';
/**
* Value to display inside the badge.
* Severity type of the badge.
* @group Props
*/
@Input() get value(): string {
return this._value;
}
set value(val: string) {
if (val !== this._value) {
this._value = val;

if (this.initialized) {
let badge: HTMLElement = document.getElementById(this.id) as HTMLElement;

if (this._value) {
if (DomHandler.hasClass(badge, 'p-badge-dot')) DomHandler.removeClass(badge, 'p-badge-dot');

if (String(this._value).length === 1) {
DomHandler.addClass(badge, 'p-badge-no-gutter');
} else {
DomHandler.removeClass(badge, 'p-badge-no-gutter');
}
} else if (!this._value && !DomHandler.hasClass(badge, 'p-badge-dot')) {
DomHandler.addClass(badge, 'p-badge-dot');
}

badge.innerHTML = '';
this.renderer.appendChild(badge, document.createTextNode(this._value));
}
}
}
@Input() public severity: 'success' | 'info' | 'warning' | 'danger' | null | undefined;
/**
* Severity type of the badge.
* Value to display inside the badge.
* @group Props
*/
@Input() severity: 'success' | 'info' | 'warning' | 'danger' | null | undefined;

public _value!: string;

public initialized: boolean = false;
@Input() public value: string | number;

private id!: string;

private _disabled: boolean = false;
private get activeElement(): HTMLElement {
return this.el.nativeElement.nodeName.indexOf('-') != -1 ? this.el.nativeElement.firstChild : this.el.nativeElement;
}

private _size!: 'large' | 'xlarge';
private get canUpdateBadge(): boolean {
return this.id && !this.disabled;
}

constructor(@Inject(DOCUMENT) private document: Document, public el: ElementRef, private renderer: Renderer2) {}

ngAfterViewInit() {
this.id = UniqueComponentId() + '_badge';
let el = this.el.nativeElement.nodeName.indexOf('-') != -1 ? this.el.nativeElement.firstChild : this.el.nativeElement;
public ngOnChanges({ value, size, severity, disabled }: SimpleChanges): void {
if (disabled) {
this.toggleDisableState();
}

if (this._disabled) {
return null;
if (!this.canUpdateBadge) {
return;
}

let badge = this.document.createElement('span');
badge.id = this.id;
badge.className = 'p-badge p-component';
if (severity) {
this.setSeverity(severity.previousValue);
}

if (this.severity) {
DomHandler.addClass(badge, 'p-badge-' + this.severity);
if (size) {
this.setSizeClasses();
}

this.setSizeClasses(badge);
if (value) {
this.setValue();
}
}

public ngAfterViewInit(): void {
this.id = UniqueComponentId() + '_badge';
this.renderBadgeContent();
}

private setValue(element?: HTMLElement): void {
const badge = element ?? this.document.getElementById(this.id);

if (!badge) {
return;
}

if (this.value != null) {
this.renderer.appendChild(badge, this.document.createTextNode(this.value));
if (DomHandler.hasClass(badge, 'p-badge-dot')) {
DomHandler.removeClass(badge, 'p-badge-dot');
}

if (String(this.value).length === 1) {
if (this.value && String(this.value).length === 1) {
DomHandler.addClass(badge, 'p-badge-no-gutter');
} else {
DomHandler.removeClass(badge, 'p-badge-no-gutter');
}
} else {
DomHandler.addClass(badge, 'p-badge-dot');
}
if (!DomHandler.hasClass(badge, 'p-badge-dot')) {
DomHandler.addClass(badge, 'p-badge-dot');
}

DomHandler.addClass(el, 'p-overlay-badge');
this.renderer.appendChild(el, badge);
DomHandler.removeClass(badge, 'p-badge-no-gutter');
}

this.initialized = true;
badge.innerHTML = '';
const badgeValue = this.value != null ? String(this.value) : '';
this.renderer.appendChild(badge, this.document.createTextNode(badgeValue));
}

private setSizeClasses(element?: HTMLElement): void {
Expand All @@ -133,13 +111,13 @@ export class BadgeDirective implements AfterViewInit, OnDestroy {
return;
}

if (this._size) {
if (this._size === 'large') {
if (this.badgeSize) {
if (this.badgeSize === 'large') {
DomHandler.addClass(badge, 'p-badge-lg');
DomHandler.removeClass(badge, 'p-badge-xl');
}

if (this._size === 'xlarge') {
if (this.badgeSize === 'xlarge') {
DomHandler.addClass(badge, 'p-badge-xl');
DomHandler.removeClass(badge, 'p-badge-lg');
}
Expand All @@ -149,8 +127,53 @@ export class BadgeDirective implements AfterViewInit, OnDestroy {
}
}

ngOnDestroy() {
this.initialized = false;
private renderBadgeContent(): void {
if (this.disabled) {
return null;
}

const el = this.activeElement;
const badge = this.document.createElement('span');
badge.id = this.id;
badge.className = 'p-badge p-component';

this.setSeverity(null, badge);
this.setSizeClasses(badge);
this.setValue(badge);
DomHandler.addClass(el, 'p-overlay-badge');
this.renderer.appendChild(el, badge);
}

private setSeverity(oldSeverity?: 'success' | 'info' | 'warning' | 'danger' | null, element?: HTMLElement): void {
const badge = element ?? this.document.getElementById(this.id);

if (!badge) {
return;
}

if (this.severity) {
DomHandler.addClass(badge, `p-badge-${this.severity}`);
}

if (oldSeverity) {
DomHandler.removeClass(badge, `p-badge-${oldSeverity}`);
}
}

private toggleDisableState(): void {
if (!this.id) {
return;
}

if (this.disabled) {
const badge = this.activeElement?.querySelector(`#${this.id}`);

if (badge) {
this.renderer.removeChild(this.activeElement, badge);
}
} else {
this.renderBadgeContent();
}
}
}
/**
Expand Down Expand Up @@ -182,7 +205,7 @@ export class Badge {
* Size of the badge, valid options are "large" and "xlarge".
* @group Props
*/
@Input() size: 'large' | 'xlarge' | undefined;
@Input() badgeSize: 'large' | 'xlarge' | undefined;
/**
* Severity type of the badge.
* @group Props
Expand All @@ -192,7 +215,7 @@ export class Badge {
* Value to display inside the badge.
* @group Props
*/
@Input() value: string | null | undefined;
@Input() value: string | number | null | undefined;
/**
* When specified, disables the component.
* @group Props
Expand All @@ -203,8 +226,8 @@ export class Badge {
return {
'p-badge p-component': true,
'p-badge-no-gutter': this.value != undefined && String(this.value).length === 1,
'p-badge-lg': this.size === 'large',
'p-badge-xl': this.size === 'xlarge',
'p-badge-lg': this.badgeSize === 'large',
'p-badge-xl': this.badgeSize === 'xlarge',
'p-badge-info': this.severity === 'info',
'p-badge-success': this.severity === 'success',
'p-badge-warning': this.severity === 'warning',
Expand Down
27 changes: 10 additions & 17 deletions src/app/showcase/doc/apidoc/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -4455,41 +4455,34 @@
"props": {
"description": "Defines the input properties of the component.",
"values": [
{
"name": "iconPos",
"optional": false,
"readonly": false,
"type": "\"left\" | \"top\" | \"bottom\" | \"right\"",
"default": "left",
"description": "Icon position of the component."
},
{
"name": "disabled",
"optional": false,
"readonly": false,
"type": "boolean",
"default": "false",
"description": "When specified, disables the component."
},
{
"name": "size",
"name": "badgeSize",
"optional": false,
"readonly": false,
"type": "\"large\" | \"xlarge\"",
"description": "Size of the badge, valid options are \"large\" and \"xlarge\"."
},
{
"name": "value",
"name": "severity",
"optional": false,
"readonly": false,
"type": "string",
"description": "Value to display inside the badge."
"type": "\"success\" | \"info\" | \"warning\" | \"danger\"",
"description": "Severity type of the badge."
},
{
"name": "severity",
"name": "value",
"optional": false,
"readonly": false,
"type": "\"success\" | \"info\" | \"warning\" | \"danger\"",
"description": "Severity type of the badge."
"type": "string | number",
"description": "Value to display inside the badge."
}
]
}
Expand All @@ -4514,7 +4507,7 @@
"description": "Inline style of the element."
},
{
"name": "size",
"name": "badgeSize",
"optional": false,
"readonly": false,
"type": "\"large\" | \"xlarge\"",
Expand All @@ -4531,7 +4524,7 @@
"name": "value",
"optional": false,
"readonly": false,
"type": "string",
"type": "string | number",
"description": "Value to display inside the badge."
},
{
Expand Down
12 changes: 6 additions & 6 deletions src/app/showcase/doc/badge/sizedoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@ import { Code } from '../../domain/code';
selector: 'badge-size-demo',
template: `
<app-docsectiontext>
<p>Badge sizes are adjusted with the <i>size</i> property that accepts <i>large</i> and <i>xlarge</i> as the possible alternatives to the default size. Currently sizes only apply to component mode.</p>
<p>Badge sizes are adjusted with the <i>badgeSize</i> property that accepts <i>large</i> and <i>xlarge</i> as the possible alternatives to the default size. Currently sizes only apply to component mode.</p>
</app-docsectiontext>
<div class="card flex justify-content-center">
<p-badge value="2"></p-badge>
<p-badge value="4" size="large" severity="warning"></p-badge>
<p-badge value="6" size="xlarge" severity="success"></p-badge>
<p-badge value="4" badgeSize="large" severity="warning"></p-badge>
<p-badge value="6" badgeSize="xlarge" severity="success"></p-badge>
</div>
<app-code [code]="code" selector="badge-size-demo"></app-code>
`
})
export class SizeDoc {
code: Code = {
basic: `<p-badge value="4" size="large" severity="warning"></p-badge>`,
basic: `<p-badge value="4" badgeSize="large" severity="warning"></p-badge>`,
html: `
<div class="card flex justify-content-center">
<p-badge value="2"></p-badge>
<p-badge value="4" size="large" severity="warning"></p-badge>
<p-badge value="6" size="xlarge" severity="success"></p-badge>
<p-badge value="4" badgeSize="large" severity="warning"></p-badge>
<p-badge value="6" badgeSize="xlarge" severity="success"></p-badge>
</div>`,
typescript: `
import { Component } from '@angular/core';
Expand Down

0 comments on commit 7b19c33

Please sign in to comment.