Skip to content

Commit

Permalink
editor: count chars and words in textarea
Browse files Browse the repository at this point in the history
* Adds counters in textarea for chars and words, depending on the configuration.
* Avoids to exceed the counts when typing.
* Checks for new resource context to avoid `pid` to be set in model.

Co-Authored-by: Sébastien Délèze <[email protected]>
  • Loading branch information
Sébastien Délèze committed Apr 13, 2021
1 parent cb37a2c commit dfb9a27
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 4 deletions.
3 changes: 2 additions & 1 deletion .prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"singleQuote": true
"singleQuote": true,
"printWidth": 120
}
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,11 @@ export class EditorComponent implements OnInit, OnChanges, OnDestroy {
// /!\ This will probably not work anymore with resources managed by
// invenio-records-resources, a fix will be necessary to make it work
// with both systems.
if (this.pid != null && (this.model == null || this.model.pid == null)) {
if (
!this._resourceConfig.recordResource &&
this.pid != null &&
(this.model == null || this.model.pid == null)
) {
model.pid = this.pid;
}
// preprocess the model before sending to formly
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* RERO angular core
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { AbstractControl, ValidatorFn } from '@angular/forms';
import { FormlyFieldTextArea } from '@ngx-formly/bootstrap';

@Component({
selector: 'ng-core-editor-formly-field-textarea',
template: `
<textarea
[formControl]="formControl"
[cols]="to.cols"
[rows]="to.rows"
class="form-control"
[class.is-invalid]="showError"
[formlyAttributes]="field"
></textarea>
<ng-container
[ngTemplateOutlet]="counter"
[ngTemplateOutletContext]="{
limit: field.templateOptions.limitWords,
count: countWords,
label: 'Number of words' | translate
}"
*ngIf="field.templateOptions.limitWords || field.templateOptions.displayWords"
></ng-container>
<ng-container
[ngTemplateOutlet]="counter"
[ngTemplateOutletContext]="{
limit: field.templateOptions.limitChars,
count: countChars,
label: 'Number of chars' | translate
}"
*ngIf="field.templateOptions.limitChars || field.templateOptions.displayChars"
></ng-container>
<ng-template #counter let-limit="limit" let-count="count" let-label="label">
<span class="small text-muted d-inline-block mr-3">
{{ label }}: {{ count }}
<ng-container *ngIf="limit"> / {{ limit }} </ng-container>
</span>
</ng-template>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
// means change detection is triggered only when @input changed.
})
export class TextareaFieldComponent extends FormlyFieldTextArea implements OnInit {
/**
* Get the number of chars.
*
* @returns The number of chars.
*/
get countChars(): number {
return !this.formControl.value ? 0 : this.formControl.value.length;
}

/**
* Get the number of words.
*
* @returns The number of words.
*/
get countWords(): number {
return !this.formControl.value ? 0 : this.formControl.value.split(/\s+/).length;
}

/**
* Component init.
*
* Adds validator for chars and words.
* Check if the current value does not exceed the limit value for chars or words.
*/
ngOnInit(): void {
if (this.field.templateOptions.limitWords || this.field.templateOptions.limitChars) {
this.formControl.setValidators([this.limitValidator(), this.formControl.validator]);
this.formControl.updateValueAndValidity();
}
}

/**
* Form validator to check if the value is not greater than the limit.
*
* @returns A validator function returning the eventual error.
*/
limitValidator(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => {
if (this.field.templateOptions.limitWords && this.countWords > this.field.templateOptions.limitWords) {
return { limitWords: { value: control.value } };
}

if (this.field.templateOptions.limitChars && this.countChars > this.field.templateOptions.limitChars) {
return { limitChars: { value: control.value } };
}

return null;
};
}
}
7 changes: 5 additions & 2 deletions projects/rero/ng-core/src/lib/record/record.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { ObjectTypeComponent } from './editor/type/object-type/object-type.compo
import { RemoteTypeaheadComponent } from './editor/type/remote-typeahead/remote-typeahead.component';
import { SelectWithSortTypeComponent } from './editor/type/select-with-sort-type.component';
import { SwitchComponent } from './editor/type/switch/switch.component';
import { TextareaFieldComponent } from './editor/type/textarea/textarea.component';
import { AddFieldEditorComponent } from './editor/widgets/add-field-editor/add-field-editor.component';
import { DropdownLabelEditorComponent } from './editor/widgets/dropdown-label-editor/dropdown-label-editor.component';
import { LabelComponent } from './editor/widgets/label/label.component';
Expand Down Expand Up @@ -99,7 +100,8 @@ import { RecordSearchResultDirective } from './search/result/record-search-resul
LoadTemplateFormComponent,
SaveTemplateFormComponent,
CardWrapperComponent,
LabelComponent
LabelComponent,
TextareaFieldComponent
],
imports: [
// NOTE : BrowserAnimationModule **should** be include in application core module.
Expand Down Expand Up @@ -147,7 +149,8 @@ import { RecordSearchResultDirective } from './search/result/record-search-resul
{ name: 'multischema', component: MultiSchemaTypeComponent },
{ name: 'datepicker', component: DatepickerTypeComponent },
{ name: 'selectWithSort', component: SelectWithSortTypeComponent },
{ name: 'remoteTypeahead', component: RemoteTypeaheadComponent }
{ name: 'remoteTypeahead', component: RemoteTypeaheadComponent },
{ name: 'textarea', component: TextareaFieldComponent }
],
wrappers: [
{ name: 'toggle-switch', component: ToggleWrapperComponent },
Expand Down

0 comments on commit dfb9a27

Please sign in to comment.