Skip to content

Commit

Permalink
fix: improved textarea-description Formly wrapper and renamed it to…
Browse files Browse the repository at this point in the history
… `maxlength-description`

* compatibility to be used together with `description` wrapper
* required `maxLength` `props` now
* `props` key to provide an alternative translation key was changed from `customDescription` to `maxLengthDescription`

BREAKING CHANGES: The Formly wrapper `textarea-description` was renamed to `maxlength-description` (see [Migrations / From 5.1 to 5.2](https://github.com/intershop/intershop-pwa/blob/develop/docs/guides/migrations.md#from-51-to-52) for more details).
  • Loading branch information
shauke committed Jun 14, 2024
1 parent 5d6a685 commit cb1366c
Show file tree
Hide file tree
Showing 12 changed files with 53 additions and 43 deletions.
20 changes: 10 additions & 10 deletions docs/guides/formly.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ You can find a simple example of a custom type test in [text-input.field.compone

To test custom wrappers, create a `FormlyTestingContainerComponent` component, configure the `FormlyModule` with an example type (for example `FormlyTestingExampleComponent`) and the wrapper and set an appropriate testing configuration.

You can find a simple example of a wrapper test in [textarea-description-wrapper.component.spec.ts](../../src/app/shared/formly/wrappers/textarea-description-wrapper/textarea-description-wrapper.component.spec.ts).
You can find a simple example of a wrapper test in [maxlength-description-wrapper.component.spec.ts](../../src/app/shared/formly/wrappers/maxlength-description-wrapper/maxlength-description-wrapper.component.spec.ts).

## How to Configure Formly

Expand Down Expand Up @@ -273,15 +273,15 @@ Refer to the tables below for an overview of these parts.

### Wrappers

| Name | Functionality | Relevant props |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| form-field-horizontal | Adds a label next to the field and adds red styling for invalid fields. | `labelClass`& `fieldClass`: Classes that will be added to the label or field `<div>` |
| form-field-checkbox-horizontal | Adds a label for a checkbox or radio field, adds red styling and error messages for invalid fields. Adds a title for a checkbox, if provided. Uses `validators.validation` and `validation.messages` properties. Adds a tooltip behind the label, see also tooltip-wrapper | `labelClass`, `titleClass`& `fieldClass`: Classes that will be added to the label, title or the outer field `<div>` |
| validation | Adds validation icons and error messages to the field. Uses `validators.validation` and `validation.messages` properties. | `showValidation`: `(field: FormlyFieldConfig) => boolean`: optional, used to determine whether to show validation check marks |
| textarea-description | Adds a description to textarea fields, including the amount of remaining characters. | `maxLength`: Specifies the maximum length to be displayed in the message. `customDescription`: translation key for the textarea description (default: 'textarea.max_limit' ) |
| description | Adds a custom description to any field | `customDescription`: `string` or `{key: string; args: any}` that will be translated |
| tooltip | Adds a tooltip to a field. Includes `<ish-field-tooltip>` component. | `tooltip`: `{ title?: string; text: string; link: string }` that defines the different tooltip texts. |
| input-addon | Adds a prepended or appended text to a field, e.g. a currency or unit. | `addonLeft?`: `{ text: string \| Observable<string>; }, addonRight?: {text: string \| Observable<string>}` that defines the addon texts. |
| Name | Functionality | Relevant props |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| form-field-horizontal | Adds a label next to the field and adds red styling for invalid fields. | `labelClass`& `fieldClass`: Classes that will be added to the label or field `<div>` |
| form-field-checkbox-horizontal | Adds a label for a checkbox or radio field, adds red styling and error messages for invalid fields. Adds a title for a checkbox, if provided. Uses `validators.validation` and `validation.messages` properties. Adds a tooltip behind the label, see also tooltip-wrapper | `labelClass`, `titleClass`& `fieldClass`: Classes that will be added to the label, title or the outer field `<div>` |
| validation | Adds validation icons and error messages to the field. Uses `validators.validation` and `validation.messages` properties. | `showValidation`: `(field: FormlyFieldConfig) => boolean`: optional, used to determine whether to show validation check marks |
| maxlength-description | Adds a description to textarea fields, including the amount of remaining characters (added to textarea by default, can be used for other fields as well). | `maxLength`: Specifies the maximum length to be displayed in the message (required). `maxLengthDescription`: translation key for the maxlength description (default: 'textarea.max_limit' ) |
| description | Adds a custom description to any field | `customDescription`: `string` or `{key: string; args: any}` that will be translated |
| tooltip | Adds a tooltip to a field. Includes `<ish-field-tooltip>` component. | `tooltip`: `{ title?: string; text: string; link: string }` that defines the different tooltip texts. |
| input-addon | Adds a prepended or appended text to a field, e.g. a currency or unit. | `addonLeft?`: `{ text: string \| Observable<string>; }, addonRight?: {text: string \| Observable<string>}` that defines the addon texts. |

### Extensions

Expand Down
10 changes: 10 additions & 0 deletions docs/guides/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ For that reason product variations are now loaded lazily through the following c
- The `productMaster` property on the product view model has been removed. The master product should be individually retrieved.
- The `ish-product-item-variations` component has been refactored.

The Formly wrapper `textarea-description` was renamed to the better suited name `maxlength-description` and the rendering logic and compatibility to the general `description` wrapper was improved.
The renaming should better convey the fact that this wrapper can be used not only for `ish-textarea-field` (where it is configured by default) but with other input field types as well.
Besides this the main difference to the general `description` wrapper is its remaining `maxlength` calculation.
Together with the renaming the implementation was changed so that the description will only be rendered if the according field has a `props.maxLength` value configured.
And the `props` key to provide an alternative translation key was changed from `customDescription` to `maxLengthDescription`.
This change provides the possibility to use the `description` wrapper with its `customDescription` together with the `maxlength-description` wrapper with a customized `maxLengthDescription`.
For the migration of customer projects it needs to be checked whether a `customDescription` is configured for a `ish-textarea-field` and if so it needs to be replaced with `maxLengthDescription`.
Additionally it needs to be checked if the `textarea-description` wrapper is configured anywhere else then the default assignment to the `ish-textarea-field`.
If so these wrapper configurations need to be replaced with `maxlength-description`.

## From 5.0 to 5.1

The OrderListComponent is strictly presentational, components using it have to supply the data.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ export class ProductReviewCreateDialogComponent implements OnInit {
labelClass: 'col-md-3',
fieldClass: 'col-md-9',
required: true,
maxLength: 4000,
rows: 5,
placeholder: 'product.review.content.placeholder',
customDescription: 'product.review.content.max_limit',
maxLength: 4000,
maxLengthDescription: 'product.review.content.max_limit',
},
validation: {
messages: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export class BasketMerchantMessageComponent implements OnInit, OnChanges {
key: 'messageToMerchant',
type: 'ish-textarea-field',
props: {
postWrappers: [{ wrapper: 'description', index: -1 }],
label: 'checkout.basket_merchant_message.label',
maxLength: 1000,
rows: 2,
Expand Down
2 changes: 1 addition & 1 deletion src/app/shared/formly/dev/testing/formly-testing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ class RepeatFieldComponent extends FieldArrayType {}
wrappers: [
{ name: 'form-field-horizontal', component: DummyWrapperComponent },
{ name: 'form-field-checkbox-horizontal', component: DummyWrapperComponent },
{ name: 'textarea-description', component: DummyWrapperComponent },
{ name: 'maxlength-description', component: DummyWrapperComponent },
{ name: 'tooltip', component: DummyWrapperComponent },
{ name: 'validation', component: DummyWrapperComponent },
{ name: 'description', component: DummyWrapperComponent },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { FieldType, FieldTypeConfig } from '@ngx-formly/core';
* @props **cols** - the amount of columns the textarea should have
* @props **rows** - the amount of rows the textarea should have
*
* @defaultWrappers form-field-horizontal & textarea-description & validation
* @defaultWrappers form-field-horizontal & maxlength-description & validation
*
* @usageNotes
* See the textarea-description wrapper for more info on the relevant props.
* See the maxlength-description wrapper for more info on the relevant props.
*/
@Component({
selector: 'ish-textarea-field',
Expand Down
2 changes: 1 addition & 1 deletion src/app/shared/formly/types/types.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ const fieldComponents = [
{
name: 'ish-textarea-field',
component: TextareaFieldComponent,
wrappers: ['form-field-horizontal', 'textarea-description', 'validation'],
wrappers: ['form-field-horizontal', 'maxlength-description', 'validation'],
},
{
name: 'ish-checkbox-field',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<ng-template #fieldComponent></ng-template>
<small *ngIf="description$ | async as desc" class="form-text" data-testing-id="maxlength-description">
{{ desc }}
</small>
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { FormlyTestingComponentsModule } from 'ish-shared/formly/dev/testing/for
import { FormlyTestingContainerComponent } from 'ish-shared/formly/dev/testing/formly-testing-container/formly-testing-container.component';
import { FormlyTestingExampleComponent } from 'ish-shared/formly/dev/testing/formly-testing-example/formly-testing-example.component';

import { TextareaDescriptionWrapperComponent } from './textarea-description-wrapper.component';
import { MaxlengthDescriptionWrapperComponent } from './maxlength-description-wrapper.component';

describe('Textarea Description Wrapper Component', () => {
describe('Maxlength Description Wrapper Component', () => {
let component: FormlyTestingContainerComponent;
let fixture: ComponentFixture<FormlyTestingContainerComponent>;
let element: HTMLElement;
Expand All @@ -21,12 +21,12 @@ describe('Textarea Description Wrapper Component', () => {
imports: [
FormlyModule.forRoot({
types: [{ name: 'textarea', component: FormlyTestingExampleComponent }],
wrappers: [{ name: 'textarea-description-wrapper', component: TextareaDescriptionWrapperComponent }],
wrappers: [{ name: 'maxlength-description-wrapper', component: MaxlengthDescriptionWrapperComponent }],
}),
FormlyTestingComponentsModule,
TranslateModule.forRoot(),
],
declarations: [TextareaDescriptionWrapperComponent],
declarations: [MaxlengthDescriptionWrapperComponent],
}).compileComponents();
});

Expand All @@ -44,7 +44,7 @@ describe('Textarea Description Wrapper Component', () => {
{
key: 'textarea',
type: 'textarea',
wrappers: ['textarea-description-wrapper'],
wrappers: ['maxlength-description-wrapper'],
props: {
maxLength: 1000,
},
Expand All @@ -65,20 +65,20 @@ describe('Textarea Description Wrapper Component', () => {

it('should be rendered after creation', () => {
fixture.detectChanges();
expect(element.querySelector('ish-textarea-description-wrapper')).toBeTruthy();
expect(element.querySelector('ish-maxlength-description-wrapper')).toBeTruthy();
});

it('should display maxLength on empty field', () => {
component.form.get('textarea')?.setValue('');
fixture.detectChanges();
expect(element.querySelector('[data-testing-id="textarea-description"]')?.textContent.trim()).toEqual('1000');
expect(element.querySelector('[data-testing-id="maxlength-description"]')?.textContent.trim()).toEqual('1000');
});

it('should display correct remaining length if field is not empty', fakeAsync(() => {
fixture.detectChanges();
component.form.get('textarea')?.setValue('0123456789');
tick(1000);
fixture.detectChanges();
expect(element.querySelector('[data-testing-id="textarea-description"]')?.textContent.trim()).toEqual('990');
expect(element.querySelector('[data-testing-id="maxlength-description"]')?.textContent.trim()).toEqual('990');
}));
});
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { FieldWrapper } from '@ngx-formly/core';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { Observable, of } from 'rxjs';
import { startWith, switchMap, throttleTime } from 'rxjs/operators';

/**
* Wrapper to display a description that counts the remaining characters in a field.
*
* @props **maxLength** - will be used to determine the remaining available characters.
* @props **maxLength** - will be used to determine the remaining available characters (required, otherwise the counter description will not be rendered).
* @props **maxLengthDescription** - an alternative translation key that can be used to customize the counter description (default: 'textarea.max_limit').
*
* @usageNotes
* This wrapper is made for the textarea type but could be used for different field types as well.
* This wrapper is made for the textarea type (and assigned to 'ish-textarea-field' by default) but could be used for different field types as well.
*/
@Component({
selector: 'ish-textarea-description-wrapper',
templateUrl: './textarea-description-wrapper.component.html',
selector: 'ish-maxlength-description-wrapper',
templateUrl: './maxlength-description-wrapper.component.html',
changeDetection: ChangeDetectionStrategy.Default,
})
export class TextareaDescriptionWrapperComponent extends FieldWrapper implements OnInit {
export class MaxlengthDescriptionWrapperComponent extends FieldWrapper implements OnInit {
description$: Observable<string>;

constructor(private translate: TranslateService) {
Expand All @@ -33,8 +34,10 @@ export class TextareaDescriptionWrapperComponent extends FieldWrapper implements
}

private getDescription$(value: string): Observable<string> {
return this.translate.get(this.props.customDescription ?? 'textarea.max_limit', {
0: Math.max(0, this.props.maxLength - (value?.length ?? 0)),
});
return this.props.maxLength
? this.translate.get(this.props.maxLengthDescription ?? 'textarea.max_limit', {
0: Math.max(0, this.props.maxLength - (value?.length ?? 0)),
})
: of(undefined);
}
}

This file was deleted.

Loading

0 comments on commit cb1366c

Please sign in to comment.