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
Fixes #12736.
Fixes #12959.
  • Loading branch information
volvachev committed May 23, 2023
1 parent 2e57322 commit a1311b0
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 93 deletions.
191 changes: 107 additions & 84 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 } from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, Component, Directive, ElementRef, Inject, Input, NgModule, Renderer2, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { SharedModule } from 'primeng/api';
import { DomHandler } from 'primeng/dom';
import { UniqueComponentId } from 'primeng/utils';
Expand All @@ -10,117 +10,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('badgeSize') public size: '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 @@ -130,13 +108,13 @@ export class BadgeDirective implements AfterViewInit, OnDestroy {
return;
}

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

if (this._size === 'xlarge') {
if (this.size === 'xlarge') {
DomHandler.addClass(badge, 'p-badge-xl');
DomHandler.removeClass(badge, 'p-badge-lg');
}
Expand All @@ -146,8 +124,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 @@ -176,7 +199,7 @@ export class Badge {
* Size of the badge, valid options are "large" and "xlarge".
* @group Props
*/
@Input() size: 'large' | 'xlarge' | undefined;
@Input('badgeSize') size: 'large' | 'xlarge' | undefined;
/**
* Severity type of the badge.
* @group Props
Expand All @@ -186,7 +209,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 Down
4 changes: 2 additions & 2 deletions src/app/components/contextmenu/contextmenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,15 +342,15 @@ export class ContextMenu implements AfterViewInit, OnDestroy {
if (this.global) {
const documentTarget: any = this.el ? this.el.nativeElement.ownerDocument : 'document';
this.triggerEventListener = this.renderer.listen(documentTarget, this.triggerEvent, (event) => {
if(this.containerViewChild && this.containerViewChild.nativeElement.style.display !== 'none') {
if (this.containerViewChild && this.containerViewChild.nativeElement.style.display !== 'none') {
this.hide();
}
this.show(event);
event.preventDefault();
});
} else if (this.target) {
this.triggerEventListener = this.renderer.listen(this.target, this.triggerEvent, (event) => {
if(this.containerViewChild && this.containerViewChild.nativeElement.style.display !== 'none') {
if (this.containerViewChild && this.containerViewChild.nativeElement.style.display !== 'none') {
this.hide();
}
this.show(event);
Expand Down
2 changes: 1 addition & 1 deletion src/app/showcase/doc/badge/propsdoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { Component, Input } from '@angular/core';
<td>Severity type of the badge.</td>
</tr>
<tr>
<td>size</td>
<td>badgeSize</td>
<td>string</td>
<td>null</td>
<td>Size of the badge, valid options are "large" and "xlarge".</td>
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,12 +5,12 @@ import { Code } from '../../domain/code';
selector: 'badge-size-demo',
template: ` <section>
<app-docsectiontext [title]="title" [id]="id">
<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>
</section>`
Expand All @@ -22,12 +22,12 @@ export class SizeDoc {

code: Code = {
basic: `
<p-badge value="4" size="large" severity="warning"></p-badge>`,
<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 a1311b0

Please sign in to comment.