Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(primeng/p-badge): rework dynamic property rerender for directive #12739

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading