Skip to content

Commit

Permalink
feat(edit-content) add search to category-field #28879
Browse files Browse the repository at this point in the history
  • Loading branch information
oidacra committed Jul 12, 2024
1 parent 5fcef38 commit 7bfff80
Show file tree
Hide file tree
Showing 21 changed files with 616 additions and 37 deletions.
5 changes: 5 additions & 0 deletions core-web/libs/dotcms-models/src/lib/dot-categories.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@ export interface DotCategory {
parentPermissionable?: {
hostname: string;
};
parentList?: Array<{
categoryName: string;
key: string;
inode: string;
}>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<div #tableContainer class="category-field__search-list">
@if (!isLoading()) {
<p-table
(selectionChange)="selected.emit($event)"
[scrollHeight]="scrollHeight()"
[scrollable]="true"
[value]="categories()"
dataKey="key"
selectionMode="multiple"
styleClass="dotTable ">
<ng-template pTemplate="header">
<tr>
<th>
<p-tableHeaderCheckbox style="width: 4rem"></p-tableHeaderCheckbox>
</th>
<th>{{ 'edit.content.category-field.search.name' | dm }}</th>
<th>{{ 'edit.content.category-field.search.assignee' | dm }}</th>
</tr>
</ng-template>

<ng-template let-category pTemplate="body">
<tr>
<td>
<p-tableCheckbox [value]="category"></p-tableCheckbox>
</td>
<td>{{ category.categoryName }}</td>
<td>{{ category.path }}</td>
</tr>
</ng-template>
</p-table>
} @else {

<dot-table-skeleton
[columns]="[
'',
'edit.content.category-field.search.name' | dm,
'edit.content.category-field.search.assignee' | dm
]" />

}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.category-field__search-list {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}

::ng-deep .dotTable.p-datatable > .p-datatable-wrapper {
border-radius: 0;
border: none;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { DotCategoryFieldSearchListComponent } from './dot-category-field-search-list.component';

describe('DotCategoryFieldSearchListComponent', () => {
let component: DotCategoryFieldSearchListComponent;
let fixture: ComponentFixture<DotCategoryFieldSearchListComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DotCategoryFieldSearchListComponent]
}).compileComponents();

fixture = TestBed.createComponent(DotCategoryFieldSearchListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { CommonModule } from '@angular/common';
import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
DestroyRef,
ElementRef,
EventEmitter,
inject,
input,
Output,
signal,
ViewChild
} from '@angular/core';

import { SkeletonModule } from 'primeng/skeleton';
import { TableModule } from 'primeng/table';

import { DotMessagePipe } from '@dotcms/ui';

import {
DotCategoryFieldCategory,
DotCategoryFieldCategorySearchedItems
} from '../../models/dot-category-field.models';
import { DotTableSkeletonComponent } from '../dot-table-skeleton/dot-table-skeleton.component';

@Component({
selector: 'dot-category-field-search-list',
standalone: true,
imports: [CommonModule, TableModule, SkeletonModule, DotTableSkeletonComponent, DotMessagePipe],
templateUrl: './dot-category-field-search-list.component.html',
styleUrl: './dot-category-field-search-list.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DotCategoryFieldSearchListComponent implements AfterViewInit {
@ViewChild('tableContainer', { static: false }) tableContainer!: ElementRef;
scrollHeight = signal<string>('0px');

/**
* Represents the required categories input for DotCategoryFieldCategory.
*
* @typedef {DotCategoryFieldCategory[]} RequiredCategories
*/
categories = input.required<DotCategoryFieldCategorySearchedItems[]>();

@Output() selected = new EventEmitter<DotCategoryFieldCategorySearchedItems[]>();

/**
* Represents the selected categories in the DotCategoryFieldCategory class.
*/
selectedCategories: DotCategoryFieldCategory;
isLoading = input.required<boolean>();
#destroyRef = inject(DestroyRef);

ngAfterViewInit(): void {
this.calculateScrollHeight();
window.addEventListener('resize', this.calculateScrollHeight.bind(this));

this.#destroyRef.onDestroy(() => {
window.removeEventListener('resize', this.calculateScrollHeight.bind(this));
});
}

private calculateScrollHeight(): void {
const containerHeight = this.tableContainer.nativeElement.offsetHeight;
this.scrollHeight.set(`${containerHeight}px`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div class="p-inputgroup w-full">
<input
#searchInput
class="category-field__search-input"
pInputText
placeholder="{{ 'edit.content.category-field.search.input.placeholder' | dm }}"
type="text" />
@if (searchInput.value && !isLoading()) {
<span (click)="clearInput()" class="p-inputgroup-addon category-field__search-icon-clear">
<i class="pi pi-times cursor-pointer"></i>
</span>
} @if (isLoading()) {
<span class="p-inputgroup-addon category-field__search-icon-loading">
<i class="pi pi-spin pi-spinner"></i>
</span>
}

<span class="p-inputgroup-addon category-field__search--icon-search">
<i class="pi pi-search"></i>
</span>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@use "variables" as *;

.category-field__search-input.p-inputtext.p-element,
.category-field__search-icon-clear,
.category-field__search-icon-loading {
border-right: none;
}

.category-field__search--icon-search,
.category-field__search-icon-clear,
.category-field__search-icon-loading {
color: $color-palette-primary;
padding: 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { DotCategoryFieldSearchComponent } from './dot-category-field-search.component';

describe('DotCategoryFieldSearchComponent', () => {
let component: DotCategoryFieldSearchComponent;
let fixture: ComponentFixture<DotCategoryFieldSearchComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DotCategoryFieldSearchComponent]
}).compileComponents();

fixture = TestBed.createComponent(DotCategoryFieldSearchComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { fromEvent } from 'rxjs';

import { CommonModule } from '@angular/common';
import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
DestroyRef,
ElementRef,
EventEmitter,
inject,
input,
Output,
ViewChild
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

import { InputTextModule } from 'primeng/inputtext';

import { debounceTime, filter, map } from 'rxjs/operators';

import { DotMessagePipe } from '@dotcms/ui';

const DEBOUNCE_TIME = 300;
const MINIMUM_CHARACTERS = 3;

@Component({
selector: 'dot-category-field-search',
standalone: true,
imports: [CommonModule, DotMessagePipe, InputTextModule],
templateUrl: './dot-category-field-search.component.html',
styleUrl: './dot-category-field-search.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DotCategoryFieldSearchComponent implements AfterViewInit {
@ViewChild('searchInput') searchInput!: ElementRef;

/**
* Represent a EventEmitter for the term the user want to filter
*
*/
@Output() term = new EventEmitter<string>();

/**
* Represent a EventEmitter to notify we want change the mode to `list`.
*/
@Output() changeMode = new EventEmitter<string>();
isLoading = input.required<boolean>();
#destroyRef = inject(DestroyRef);

ngAfterViewInit(): void {
this.listenInputChanges();
}

/**
* Clears the value of the search input field and emits a change mode event.
*
* @return {void}
*/
clearInput(): void {
this.searchInput.nativeElement.value = '';
this.changeMode.emit('list');
}

/**
* Sets up the search input observable to listen to input events,
* debounce the input, filter by minimum character length,
* and emit the search query value.
*
* @private
*/
private listenInputChanges(): void {
fromEvent(this.searchInput.nativeElement, 'input')
.pipe(
takeUntilDestroyed(this.#destroyRef),
map((event: Event) => (event.target as HTMLInputElement).value),
debounceTime(DEBOUNCE_TIME),
filter((value: string) => value.length >= MINIMUM_CHARACTERS)
)
.subscribe((value: string) => {
this.term.emit(value);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,34 @@

<div class="category-field__content h-full w-full">
<div class="category-field__left-pane flex flex-column">
<div class="category-field__search">Input Search</div>
<div class="category-field__search">
<dot-category-field-search
(changeMode)="store.setMode($event)"
(term)="store.search($event)"
@fadeAnimation
[isLoading]="store.isSearchLoading()" />
</div>
<div class="flex-grow-1 category-field__categories">
@if (store.mode() === 'list') {
<dot-category-field-category-list
(itemChecked)="store.updateSelected($event.selected, $event.item)"
(itemClicked)="store.getCategories($event)"
[categories]="store.categoryList()"
[selected]="store.selectedCategoriesValues()" />
} @else {
<dot-category-field-search-list
@fadeAnimation
[isLoading]="store.isSearchLoading()"
[categories]="store.getSearchedCategories()"
(selected)="selectedSearch($event)" />
}
</div>
</div>

<div class="category-field__right-pane flex flex-column">
<div class="category-field__selected-categories-list flex-1">Selected Categories</div>
<div class="category-field__selected-categories-list flex-1">
Selected Categories Component
</div>
<div class="category-field__actions flex justify-content-end">
<button class="p-button p-button-link" data-testId="clear_all-btn" pButton>
{{ 'edit.content.category-field.sidebar.button.clear-all' | dm }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
gap: $spacing-1;
}

.category-field__search,
.category-field__categories,
.category-field__right-pane {
border: $field-border-size solid $color-palette-gray-400;
Expand Down
Loading

0 comments on commit 7bfff80

Please sign in to comment.