Skip to content

Commit

Permalink
feat(edit-content) Add Tags Field #26526
Browse files Browse the repository at this point in the history
* dev (dot select item directive): create directive to add items to autocomplete on enter

* dev (add styles classed dialog): add the directive on template builder autocomplete

* fix (autocomplete styles): fix spacings of util icons

* dev (edit content tag component): create component for tag

* dev (edit content): add tag component to form

* fix (edit content form): save contentlet was not working for tags

* fix (edit content): fix after merge conflcits

* dev (autocomplete styles): fix positioning of icons and buttons

* dev (autocomplete styles): fix margin

* clean up (functon utils): reduce cyclomatic complexity

* clean up: (autocomplete styles)
  • Loading branch information
zJaaal authored and dsolistorres committed Nov 6, 2023
1 parent 341b358 commit 97a64fb
Show file tree
Hide file tree
Showing 19 changed files with 334 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,32 @@ p-autocomplete.p-inputwrapper-focus {
}
}

.p-fluid .p-autocomplete.p-component, // To override the default behavior
.p-autocomplete {
display: grid;
align-items: center;
grid-template-columns: auto repeat(3, max-content); // This will make the grid to remove the columns that are not in the grid
}

.p-autocomplete {
@extend #form-field-extend;
height: auto;
padding-right: 0;

.p-autocomplete-loader,
.p-autocomplete-clear-icon {
right: $spacing-2;
position: unset;
color: $color-palette-primary;
margin-top: -0.6rem; // Couldn't use flex for this
margin: 0 $spacing-0;
}

.p-autocomplete-clear-icon {
grid-column-start: 3;
grid-row-start: 1;
}

.p-autocomplete-loader {
grid-column-start: 2;
}

.p-autocomplete-input {
Expand Down Expand Up @@ -112,7 +128,7 @@ p-autocomplete.ng-dirty.ng-invalid > .p-autocomplete > .p-inputtext {

.p-autocomplete-dropdown.p-element.p-button {
@extend #field-trigger;
height: inherit;
grid-column-start: 4;

.p-button-icon {
@extend #field-trigger-icon;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,10 @@
[field]="field"
[attr.data-testId]="'field-' + field.variable">
</dot-edit-content-calendar-field>

<dot-edit-content-tag-field
*ngSwitchCase="fieldTypes.TAG"
[field]="field"
[attr.data-testId]="'field-' + field.variable" />
</ng-container>
<small *ngIf="field.hint" [attr.data-testId]="'hint-' + field.variable">{{ field.hint }}</small>
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { DotEditContentCheckboxFieldComponent } from '../../fields/dot-edit-cont
import { DotEditContentMultiSelectFieldComponent } from '../../fields/dot-edit-content-multi-select-field/dot-edit-content-multi-select-field.component';
import { DotEditContentRadioFieldComponent } from '../../fields/dot-edit-content-radio-field/dot-edit-content-radio-field.component';
import { DotEditContentSelectFieldComponent } from '../../fields/dot-edit-content-select-field/dot-edit-content-select-field.component';
import { DotEditContentTagFieldComponent } from '../../fields/dot-edit-content-tag-field/dot-edit-content-tag-field.component';
import { DotEditContentTextAreaComponent } from '../../fields/dot-edit-content-text-area/dot-edit-content-text-area.component';
import { DotEditContentTextFieldComponent } from '../../fields/dot-edit-content-text-field/dot-edit-content-text-field.component';
import { FIELD_TYPES } from '../../models/dot-edit-content-field.enum';
Expand All @@ -28,6 +29,7 @@ const FIELD_TYPES_COMPONENTS: Record<FIELD_TYPES, Type<unknown>> = {
[FIELD_TYPES.DATE]: DotEditContentCalendarFieldComponent,
[FIELD_TYPES.DATE_AND_TIME]: DotEditContentCalendarFieldComponent,
[FIELD_TYPES.TIME]: DotEditContentCalendarFieldComponent,
[FIELD_TYPES.TAG]: DotEditContentTagFieldComponent,
[FIELD_TYPES.CHECKBOX]: DotEditContentCheckboxFieldComponent,
[FIELD_TYPES.MULTI_SELECT]: DotEditContentMultiSelectFieldComponent
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,13 @@ import { MockDotMessageService } from '@dotcms/utils-testing';

import { DotEditContentFormComponent } from './dot-edit-content-form.component';

import { EditContentFormData } from '../../models/dot-edit-content-form.interface';
import { JUST_FIELDS_MOCKS, LAYOUT_MOCK } from '../../utils/mocks';
import {
CONTENT_FORM_DATA_MOCK,
JUST_FIELDS_MOCKS,
LAYOUT_FIELDS_VALUES_MOCK
} from '../../utils/mocks';
import { DotEditContentFieldComponent } from '../dot-edit-content-field/dot-edit-content-field.component';

export const VALUES_MOCK = {
name1: 'Name1',
text2: 'Text2'
};

export const CONTENT_FORM_DATA_MOCK: EditContentFormData = {
layout: LAYOUT_MOCK,
fields: JUST_FIELDS_MOCKS
};

describe('DotFormComponent', () => {
let spectator: Spectator<DotEditContentFormComponent>;
const createComponent = createComponentFactory({
Expand Down Expand Up @@ -48,7 +41,8 @@ describe('DotFormComponent', () => {
expect(spectator.component.form.value).toEqual({
name1: 'Placeholder',
text2: null,
text3: null
text3: null,
someTag: ['some', 'tags', 'separated', 'by', 'comma']
});
});

Expand All @@ -74,7 +68,7 @@ describe('DotFormComponent', () => {
it('should have 1 row, 2 columns and 3 fields', () => {
expect(spectator.queryAll(byTestId('row'))).toHaveLength(1);
expect(spectator.queryAll(byTestId('column'))).toHaveLength(2);
expect(spectator.queryAll(byTestId('field'))).toHaveLength(3);
expect(spectator.queryAll(byTestId('field'))).toHaveLength(4);
});

it('should pass field to attr to dot-edit-content-field', () => {
Expand All @@ -90,7 +84,7 @@ describe('DotFormComponent', () => {
spectator.click(button);

expect(spectator.component.formSubmit.emit).toHaveBeenCalledWith(
spectator.component.form.value
LAYOUT_FIELDS_VALUES_MOCK
);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { DotEditContentCheckboxFieldComponent } from './dot-edit-content-checkbo
import { DotEditContentMultiSelectFieldComponent } from './dot-edit-content-multi-select-field/dot-edit-content-multi-select-field.component';
import { DotEditContentRadioFieldComponent } from './dot-edit-content-radio-field/dot-edit-content-radio-field.component';
import { DotEditContentSelectFieldComponent } from './dot-edit-content-select-field/dot-edit-content-select-field.component';
import { DotEditContentTagFieldComponent } from './dot-edit-content-tag-field/dot-edit-content-tag-field.component';
import { DotEditContentTextAreaComponent } from './dot-edit-content-text-area/dot-edit-content-text-area.component';
import { DotEditContentTextFieldComponent } from './dot-edit-content-text-field/dot-edit-content-text-field.component';

Expand All @@ -16,6 +17,7 @@ import { DotEditContentTextFieldComponent } from './dot-edit-content-text-field/
DotEditContentSelectFieldComponent,
DotEditContentTextFieldComponent,
DotEditContentCalendarFieldComponent,
DotEditContentTagFieldComponent,
DotEditContentCheckboxFieldComponent,
DotEditContentMultiSelectFieldComponent
],
Expand All @@ -25,6 +27,7 @@ import { DotEditContentTextFieldComponent } from './dot-edit-content-text-field/
DotEditContentSelectFieldComponent,
DotEditContentTextFieldComponent,
DotEditContentCalendarFieldComponent,
DotEditContentTagFieldComponent,
DotEditContentCheckboxFieldComponent,
DotEditContentMultiSelectFieldComponent
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- TODO: We need to review the tags endpoint and use it here. -->
<p-autoComplete
[formControlName]="field.variable"
[id]="field.variable"
[inputId]="field.variable"
[attr.data-testId]="field.variable"
[suggestions]="[]"
[multiple]="true"
[unique]="true"
[showClear]="true"
dotSelectItem></p-autoComplete>
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { describe, expect, test } from '@jest/globals';
import { Spectator, byTestId, createComponentFactory } from '@ngneat/spectator';

import { ControlContainer, FormGroupDirective } from '@angular/forms';
import { By } from '@angular/platform-browser';

import { AutoComplete } from 'primeng/autocomplete';

import { DotEditContentTagFieldComponent } from './dot-edit-content-tag-field.component';

import { createFormGroupDirectiveMock, TAG_FIELD_MOCK } from '../../utils/mocks';

describe('DotEditContentTagFieldComponent', () => {
let spectator: Spectator<DotEditContentTagFieldComponent>;
let autoCompleteElement: Element;
let autoCompleteComponent: AutoComplete;

const createComponent = createComponentFactory({
component: DotEditContentTagFieldComponent,
componentViewProviders: [
{
provide: ControlContainer,
useValue: createFormGroupDirectiveMock()
}
],
providers: [FormGroupDirective]
});

beforeEach(() => {
spectator = createComponent({
props: {
field: TAG_FIELD_MOCK
}
});

autoCompleteElement = spectator.query(byTestId(TAG_FIELD_MOCK.variable));

autoCompleteComponent = spectator.debugElement.query(
By.css(`[data-testId="${TAG_FIELD_MOCK.variable}"]`)
).componentInstance;
});

test.each([
{
variable: TAG_FIELD_MOCK.variable,
attribute: 'id'
},
{
variable: TAG_FIELD_MOCK.variable,
attribute: 'ng-reflect-name'
}
])('should have the $variable as $attribute', ({ variable, attribute }) => {
expect(autoCompleteElement.getAttribute(attribute)).toBe(variable);
});

it('should has multiple as true', () => {
expect(autoCompleteComponent.multiple).toBe(true);
});

it('should has unique as true', () => {
expect(autoCompleteComponent.unique).toBe(true);
});

it('should has showClear as true', () => {
expect(autoCompleteComponent.showClear).toBe(true);
});

it('should trigger selectItem on enter pressed', () => {
const selectItemMock = jest.spyOn(autoCompleteComponent, 'selectItem');

spectator.triggerEventHandler('p-autocomplete', 'onKeyUp', {
key: 'Enter',
target: {
value: 'test'
}
});

expect(selectItemMock).toBeCalledWith('test');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core';
import { ControlContainer, ReactiveFormsModule } from '@angular/forms';

import { AutoCompleteModule } from 'primeng/autocomplete';

import { DotCMSContentTypeField } from '@dotcms/dotcms-models';
import { DotSelectItemDirective } from '@dotcms/ui';

@Component({
selector: 'dot-edit-content-tag-field',
standalone: true,
imports: [CommonModule, AutoCompleteModule, DotSelectItemDirective, ReactiveFormsModule],
templateUrl: './dot-edit-content-tag-field.component.html',
styleUrls: ['./dot-edit-content-tag-field.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
viewProviders: [
{
provide: ControlContainer,
useFactory: () => inject(ControlContainer, { skipSelf: true })
}
]
})
export class DotEditContentTagFieldComponent {
@Input() field: DotCMSContentTypeField;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ import { FIELD_TYPES } from './dot-edit-content-field.enum';

export const CALENDAR_FIELD_TYPES = [FIELD_TYPES.DATE, FIELD_TYPES.DATE_AND_TIME, FIELD_TYPES.TIME];

export const FLATTENED_FIELD_TYPES = [FIELD_TYPES.CHECKBOX, FIELD_TYPES.MULTI_SELECT];
export const FLATTENED_FIELD_TYPES = [
FIELD_TYPES.CHECKBOX,
FIELD_TYPES.MULTI_SELECT,
FIELD_TYPES.TAG
];
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export enum FIELD_TYPES {
DATE = 'Date',
DATE_AND_TIME = 'Date-and-Time',
TIME = 'Time',
TAG = 'Tag',
CHECKBOX = 'Checkbox',
MULTI_SELECT = 'Multi-Select'
}
14 changes: 4 additions & 10 deletions core-web/libs/edit-content/src/lib/utils/functions.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,7 @@ export const getSingleSelectableFieldOptions = (
}

const result = lines?.map((line) => {
const [label, value] = line.split('|');
if (!value) {
return { label, value: castSingleSelectableValue(label, dataType) };
}
const [label, value = label] = line.split('|');

return { label, value: castSingleSelectableValue(value, dataType) };
});
Expand All @@ -55,7 +52,7 @@ export const getSingleSelectableFieldOptions = (
};

// This function is used to cast the value to a correct type for the Angular Form
export const getFinalCastedValue = (value: string | null, field: DotCMSContentTypeField) => {
export const getFinalCastedValue = (value: string | undefined, field: DotCMSContentTypeField) => {
if (CALENDAR_FIELD_TYPES.includes(field.fieldType as FIELD_TYPES)) {
const parseResult = new Date(value);

Expand All @@ -65,11 +62,8 @@ export const getFinalCastedValue = (value: string | null, field: DotCMSContentTy
}

if (FLATTENED_FIELD_TYPES.includes(field.fieldType as FIELD_TYPES)) {
return value.split(',');
return value?.split(',').map((value) => value.trim());
}

return (
castSingleSelectableValue(value, field.dataType) ??
castSingleSelectableValue(value, field.dataType)
);
return castSingleSelectableValue(value, field.dataType);
};
Loading

0 comments on commit 97a64fb

Please sign in to comment.