diff --git a/docs/content-services/components/search-facet-chip-tabbed.md b/docs/content-services/components/search-facet-chip-tabbed.md
new file mode 100644
index 0000000000..008871074b
--- /dev/null
+++ b/docs/content-services/components/search-facet-chip-tabbed.md
@@ -0,0 +1,49 @@
+---
+Title: Search facet chip tabbed component
+Added: v6.2.0
+Status: Active
+Last reviewed: 2023-07-18
+---
+
+# [Search facet chip tabbed component](../../../lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.ts "Defined in search-facet-chip-tabbed.component.ts")
+
+Implements a [facet widget](../../../lib/content-services/src/lib/search/models/facet-widget.interface.ts) consisting of creator and modifier facets inside tabbed component.
+
+
+
+## Basic usage
+When both creator and modifier facets are present in config file as stated below they will be merged into this component.
+
+```json
+{
+ "search": {
+ "facetFields": {
+ "fields": [
+ {
+ "mincount": 1,
+ "field": "creator",
+ "label": "SEARCH.FACET_FIELDS.CREATOR",
+ },
+ {
+ "mincount": 1,
+ "field": "modifier",
+ "label": "SEARCH.FACET_FIELDS.MODIFIER",
+ }
+ ]
+ }
+ }
+}
+```
+
+### Settings
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tabbedFacet | [TabbedFacetField](../../../lib/content-services/src/lib/search/models/tabbed-facet-field.interface.ts) | Tabbed facet configuration containing label, fields and facets to display. Required value |
+
+## See also
+
+- [Search Configuration Guide](../../user-guide/search-configuration-guide.md)
+- [Search Query Builder service](../services/search-query-builder.service.md)
+- [Search Widget Interface](../interfaces/search-widget.interface.md)
+- [Search chip autocomplete input component](search-chip-autocomplete-input.component.md)
diff --git a/docs/docassets/images/search-facet-chip-tabbed.png b/docs/docassets/images/search-facet-chip-tabbed.png
new file mode 100644
index 0000000000..d48babab5d
Binary files /dev/null and b/docs/docassets/images/search-facet-chip-tabbed.png differ
diff --git a/docs/versionIndex.md b/docs/versionIndex.md
index b7940419cd..887aa2fd8b 100644
--- a/docs/versionIndex.md
+++ b/docs/versionIndex.md
@@ -51,6 +51,7 @@ backend services have been tested with each released version of ADF.
- [Search Date Range Advanced Component](content-services/components/search-date-range-advanced.component.md)
- [Search Date Range Advanced Tabbed Component](content-services/components/search-date-range-advanced-tabbed.component.md)
- [Search Filter Tabbed Component](content-services/components/search-filter-tabbed.component.md)
+- [Search Facet Chip Tabbed Component](content-services/components/search-facet-chip-tabbed.md)
diff --git a/lib/content-services/src/lib/i18n/en.json b/lib/content-services/src/lib/i18n/en.json
index 8162a3d4f1..07635f268a 100644
--- a/lib/content-services/src/lib/i18n/en.json
+++ b/lib/content-services/src/lib/i18n/en.json
@@ -336,7 +336,12 @@
"SEARCH_FILTER": "Search Filter List",
"OPTIONS-SELECTION": "Options Selection"
},
- "ANY": "Any"
+ "ANY": "Any",
+ "PEOPLE": "People"
+ },
+ "FACET_FIELDS": {
+ "MODIFIER_LABEL": "Modified by",
+ "CREATOR_LABEL": "Created by"
},
"ICONS": {
"ft_ic_raster_image": "Image file",
diff --git a/lib/content-services/src/lib/search/components/search-chip-autocomplete-input/search-chip-autocomplete-input.component.ts b/lib/content-services/src/lib/search/components/search-chip-autocomplete-input/search-chip-autocomplete-input.component.ts
index 45666f4077..6098b3ef73 100644
--- a/lib/content-services/src/lib/search/components/search-chip-autocomplete-input/search-chip-autocomplete-input.component.ts
+++ b/lib/content-services/src/lib/search/components/search-chip-autocomplete-input/search-chip-autocomplete-input.component.ts
@@ -105,7 +105,7 @@ export class SearchChipAutocompleteInputComponent implements OnInit, OnDestroy,
ngOnChanges(changes: SimpleChanges) {
if (changes.autocompleteOptions) {
- this.filteredOptions = changes.autocompleteOptions.currentValue.length > 0 ? this.filter(changes.autocompleteOptions.currentValue, this.formCtrl.value) : [];
+ this.filteredOptions = changes.autocompleteOptions.currentValue?.length > 0 ? this.filter(changes.autocompleteOptions.currentValue, this.formCtrl.value) : [];
}
}
diff --git a/lib/content-services/src/lib/search/components/search-facet-field/search-facet-field.component.spec.ts b/lib/content-services/src/lib/search/components/search-facet-field/search-facet-field.component.spec.ts
index 7e4d01cfa7..687dd795a1 100644
--- a/lib/content-services/src/lib/search/components/search-facet-field/search-facet-field.component.spec.ts
+++ b/lib/content-services/src/lib/search/components/search-facet-field/search-facet-field.component.spec.ts
@@ -54,15 +54,15 @@ describe('SearchFacetFieldComponent', () => {
spyOn(queryBuilder, 'addUserFacetBucket').and.callThrough();
const event: any = { checked: true };
- const field: FacetField = { field: 'f1', label: 'f1', buckets: new SearchFilterList() };
+ const facetField: FacetField = { field: 'f1', label: 'f1', buckets: new SearchFilterList() };
const bucket: FacetFieldBucket = { checked: false, filterQuery: 'q1', label: 'q1', count: 1 };
- component.field = field;
+ component.field = facetField;
fixture.detectChanges();
- component.onToggleBucket(event, field, bucket);
+ component.onToggleBucket(event, facetField, bucket);
expect(bucket.checked).toBeTruthy();
- expect(queryBuilder.addUserFacetBucket).toHaveBeenCalledWith(field, bucket);
+ expect(queryBuilder.addUserFacetBucket).toHaveBeenCalledWith(facetField.field, bucket);
expect(queryBuilder.update).toHaveBeenCalled();
expect(searchFacetFiltersService.updateSelectedBuckets).toHaveBeenCalled();
});
@@ -72,15 +72,15 @@ describe('SearchFacetFieldComponent', () => {
spyOn(queryBuilder, 'removeUserFacetBucket').and.callThrough();
const event: any = { checked: false };
- const field: FacetField = { field: 'f1', label: 'f1', buckets: new SearchFilterList() };
+ const facetField: FacetField = { field: 'f1', label: 'f1', buckets: new SearchFilterList() };
const bucket: FacetFieldBucket = { checked: true, filterQuery: 'q1', label: 'q1', count: 1 };
- component.field = field;
+ component.field = facetField;
fixture.detectChanges();
- component.onToggleBucket(event, field, bucket);
+ component.onToggleBucket(event, facetField, bucket);
- expect(queryBuilder.removeUserFacetBucket).toHaveBeenCalledWith(field, bucket);
+ expect(queryBuilder.removeUserFacetBucket).toHaveBeenCalledWith(facetField.field, bucket);
expect(queryBuilder.update).toHaveBeenCalled();
expect(searchFacetFiltersService.updateSelectedBuckets).toHaveBeenCalled();
});
@@ -91,15 +91,15 @@ describe('SearchFacetFieldComponent', () => {
const event: any = { checked: false };
const query = { checked: true, label: 'q1', filterQuery: 'query1' };
- const field = { field: 'q1', type: 'query', label: 'label1', buckets: new SearchFilterList([ query ] ) } as FacetField;
+ const facetField = { field: 'q1', type: 'query', label: 'label1', buckets: new SearchFilterList([ query ] ) } as FacetField;
- component.field = field;
+ component.field = facetField;
fixture.detectChanges();
- component.onToggleBucket(event, field, query as any);
+ component.onToggleBucket(event, facetField, query as any);
expect(query.checked).toEqual(false);
- expect(queryBuilder.removeUserFacetBucket).toHaveBeenCalledWith(field, query);
+ expect(queryBuilder.removeUserFacetBucket).toHaveBeenCalledWith(facetField.field, query);
expect(queryBuilder.update).toHaveBeenCalled();
expect(searchFacetFiltersService.updateSelectedBuckets).toHaveBeenCalled();
});
diff --git a/lib/content-services/src/lib/search/components/search-facet-field/search-facet-field.component.ts b/lib/content-services/src/lib/search/components/search-facet-field/search-facet-field.component.ts
index 568e74bb3f..d81e6a42ec 100644
--- a/lib/content-services/src/lib/search/components/search-facet-field/search-facet-field.component.ts
+++ b/lib/content-services/src/lib/search/components/search-facet-field/search-facet-field.component.ts
@@ -61,7 +61,7 @@ export class SearchFacetFieldComponent implements FacetWidget {
selectFacetBucket(field: FacetField, bucket: FacetFieldBucket) {
if (bucket) {
bucket.checked = true;
- this.queryBuilder.addUserFacetBucket(field, bucket);
+ this.queryBuilder.addUserFacetBucket(field.field, bucket);
this.searchFacetFiltersService.updateSelectedBuckets();
if (this.canUpdateOnChange) {
this.updateDisplayValue();
@@ -73,7 +73,7 @@ export class SearchFacetFieldComponent implements FacetWidget {
unselectFacetBucket(field: FacetField, bucket: FacetFieldBucket) {
if (bucket) {
bucket.checked = false;
- this.queryBuilder.removeUserFacetBucket(field, bucket);
+ this.queryBuilder.removeUserFacetBucket(field.field, bucket);
this.searchFacetFiltersService.updateSelectedBuckets();
if (this.canUpdateOnChange) {
this.updateDisplayValue();
@@ -93,7 +93,7 @@ export class SearchFacetFieldComponent implements FacetWidget {
if (field && field.buckets) {
for (const bucket of field.buckets.items) {
bucket.checked = false;
- this.queryBuilder.removeUserFacetBucket(field, bucket);
+ this.queryBuilder.removeUserFacetBucket(field.field, bucket);
}
this.searchFacetFiltersService.updateSelectedBuckets();
if (this.canUpdateOnChange) {
diff --git a/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.html b/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.html
new file mode 100644
index 0000000000..0e85bd9186
--- /dev/null
+++ b/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.html
@@ -0,0 +1,59 @@
+
+
+ {{ tabbedFacet.label | translate }}:
+
+
+ {{ displayValue | translate }}
+
+ {{ 'SEARCH.FILTER.ANY' | translate }}
+ {{ chipIcon }}
+
+ remove
+
+
+
+
diff --git a/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.scss b/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.scss
new file mode 100644
index 0000000000..fcc7c885c3
--- /dev/null
+++ b/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.scss
@@ -0,0 +1,17 @@
+adf-search-facet-chip-tabbed {
+ .adf-search-filter-chip-tabbed {
+ &[disabled] {
+ pointer-events: none;
+ }
+ }
+
+ .adf-search-widget-extra-width {
+ max-width: 500px;
+ }
+}
+
+adf-search-filter-tabbed {
+ .mat-tab-body-wrapper {
+ margin-top: 16px;
+ }
+}
diff --git a/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.spec.ts b/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.spec.ts
new file mode 100644
index 0000000000..1ad66e4f6b
--- /dev/null
+++ b/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.spec.ts
@@ -0,0 +1,232 @@
+/*!
+ * @license
+ * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ContentTestingModule } from '../../../../testing/content.testing.module';
+import { TranslateModule } from '@ngx-translate/core';
+import { By } from '@angular/platform-browser';
+import { SearchQueryBuilderService } from '../../../services/search-query-builder.service';
+import { SearchFilterList } from '../../../models/search-filter-list.model';
+import { SearchFacetChipTabbedComponent } from './search-facet-chip-tabbed.component';
+import { FacetField } from '../../../models/facet-field.interface';
+import { SearchFacetFiltersService } from '../../../services/search-facet-filters.service';
+import { SimpleChange } from '@angular/core';
+
+describe('SearchFacetChipTabbedComponent', () => {
+ let component: SearchFacetChipTabbedComponent;
+ let fixture: ComponentFixture;
+ let queryBuilder: SearchQueryBuilderService;
+ let searchFacetService: SearchFacetFiltersService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [TranslateModule.forRoot(), ContentTestingModule]
+ });
+ fixture = TestBed.createComponent(SearchFacetChipTabbedComponent);
+ component = fixture.componentInstance;
+ queryBuilder = TestBed.inject(SearchQueryBuilderService);
+ searchFacetService = TestBed.inject(SearchFacetFiltersService);
+ spyOn(queryBuilder, 'update').and.stub();
+
+ const facet1: FacetField = { type: 'field', label: 'field', field: 'field', buckets: new SearchFilterList() };
+ const facet2: FacetField = { type: 'field', label: 'field2', field: 'field2', buckets: new SearchFilterList() };
+
+ component.tabbedFacet = {
+ fields: ['field', 'field2'],
+ label: 'LABEL',
+ facets: {
+ field: facet1,
+ field2: facet2
+ }
+ };
+ fixture.detectChanges();
+ });
+
+ function openFacet() {
+ const chip = fixture.debugElement.query(By.css('mat-chip'));
+ chip.triggerEventHandler('click', {});
+ fixture.detectChanges();
+ }
+
+ function getDisplayValue(): string {
+ return fixture.debugElement.query(By.css('.adf-search-filter-ellipsis.adf-filter-value')).nativeElement.innerText.trim();
+ }
+
+ function getTabs(): HTMLDivElement[] {
+ return fixture.debugElement.queryAll(By.css('.mat-tab-label-content')).map((element) => element.nativeElement);
+ }
+
+ function changeTab(tabIndex: number) {
+ getTabs()[tabIndex].click();
+ fixture.detectChanges();
+ }
+
+ function triggerComponentChanges() {
+ component.ngOnChanges({
+ tabbedFacet: new SimpleChange(null, component.tabbedFacet, false)
+ });
+ fixture.detectChanges();
+ }
+
+ function addBucketItem(field: string, displayValue: string) {
+ component.tabbedFacet.facets[field].buckets.items.push({
+ count: 1,
+ label: displayValue,
+ display: displayValue,
+ filterQuery: ''
+ });
+ triggerComponentChanges();
+ }
+
+ it('should display correct label for tabbed facet', () => {
+ const label = fixture.debugElement.query(By.css('.adf-search-filter-placeholder')).nativeElement.innerText;
+ expect(label).toBe(component.tabbedFacet.label + ':');
+ });
+
+ it('should display any as display value when nothing is selected', () => {
+ const displayValue = getDisplayValue();
+ expect(displayValue).toBe('SEARCH.FILTER.ANY');
+ });
+
+ it('should display remove icon and disable facet when no items are loaded', () => {
+ const chip = fixture.debugElement.query(By.css('mat-chip'));
+ const icon = fixture.debugElement.query(By.css('mat-chip mat-icon')).nativeElement.innerText;
+ expect(chip.classes['mat-chip-disabled']).toBeTrue();
+ expect(icon).toEqual('remove');
+ });
+
+ it('should not open context menu when no items are loaded', () => {
+ spyOn(component.menuTrigger, 'openMenu');
+ const chip = fixture.debugElement.query(By.css('mat-chip')).nativeElement;
+ chip.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }));
+ expect(component.menuTrigger.openMenu).not.toHaveBeenCalled();
+ });
+
+ it('should display correct title when facet is opened', () => {
+ openFacet();
+ const title = fixture.debugElement.query(By.css('.adf-search-filter-title')).nativeElement.innerText.split('\n')[0];
+ expect(title).toBe(component.tabbedFacet.label);
+ });
+
+ it('should display 2 tabs with specific labels', () => {
+ openFacet();
+ const tabLabels = getTabs();
+ expect(tabLabels.length).toBe(2);
+ expect(tabLabels[0].innerText).toBe(component.tabbedFacet.facets['field'].label);
+ expect(tabLabels[1].innerText).toBe(component.tabbedFacet.facets['field2'].label);
+ });
+
+ it('should display creator tab as active initially and allow navigation', () => {
+ openFacet();
+ let activeTabLabel = fixture.debugElement.query(By.css('.mat-tab-label-active .mat-tab-label-content')).nativeElement.innerText;
+ expect(activeTabLabel).toBe(component.tabbedFacet.facets['field'].label);
+
+ changeTab(1);
+ activeTabLabel = fixture.debugElement.query(By.css('.mat-tab-label-active .mat-tab-label-content')).nativeElement.innerText;
+ expect(activeTabLabel).toBe(component.tabbedFacet.facets['field2'].label);
+ });
+
+ it('should display arrow down icon and not disable the chip when items are loaded', () => {
+ addBucketItem('field', 'test');
+ const chip = fixture.debugElement.query(By.css('mat-chip'));
+ const icon = fixture.debugElement.query(By.css('mat-chip mat-icon')).nativeElement.innerText;
+ expect(chip.classes['mat-chip-disabled']).toBeUndefined();
+ expect(icon).toEqual('keyboard_arrow_down');
+ });
+
+ it('should display arrow up icon when menu is opened', () => {
+ addBucketItem('field', 'test');
+ openFacet();
+ const icon = fixture.debugElement.query(By.css('mat-chip mat-icon')).nativeElement.innerText;
+ expect(icon).toEqual('keyboard_arrow_up');
+ });
+
+ it('should create empty selected options for each tab initially', () => {
+ expect(component.selectedOptions['field']).toEqual([]);
+ expect(component.selectedOptions['field2']).toEqual([]);
+ });
+
+ it('should update autocomplete options when buckets change', () => {
+ addBucketItem('field', 'test');
+ addBucketItem('field2', 'test2');
+ expect(component.autocompleteOptions['field'].length).toBe(1);
+ expect(component.autocompleteOptions['field'][0]).toEqual({value: 'test'});
+ expect(component.autocompleteOptions['field2'].length).toBe(1);
+ expect(component.autocompleteOptions['field2'][0]).toEqual({value: 'test2'});
+ });
+
+ it('should add buckets when items are selected', () => {
+ spyOn(queryBuilder, 'addUserFacetBucket');
+ addBucketItem('field', 'test');
+ addBucketItem('field2', 'test2');
+ component.onOptionsChange([{ value: 'test' }], 'field');
+ expect(queryBuilder.addUserFacetBucket).toHaveBeenCalledWith('field',component.tabbedFacet.facets['field'].buckets.items[0]);
+ });
+
+ it('should remove buckets when items are unselected', () => {
+ spyOn(queryBuilder, 'removeUserFacetBucket');
+ addBucketItem('field', 'test');
+ addBucketItem('field2', 'test2');
+ component.onOptionsChange([], 'field');
+ expect(queryBuilder.removeUserFacetBucket).toHaveBeenCalledWith('field',component.tabbedFacet.facets['field'].buckets.items[0]);
+ });
+
+ it('should update display value when next elements are selected', () => {
+ const selectedOption1 = 'test';
+ const selectedOption2 = 'test2';
+ addBucketItem('field', selectedOption1);
+ addBucketItem('field', selectedOption2);
+ component.onOptionsChange([{ value: selectedOption1 }, { value: selectedOption2 }],'field');
+ fixture.detectChanges();
+ expect(getDisplayValue()).toBe(`${component.tabbedFacet.facets['field'].label}_LABEL: ${selectedOption1}, ${selectedOption2}`);
+ });
+
+ it('should update display value when elements from both tabs are selected', () => {
+ const selectedOption1 = 'test';
+ const selectedOption2 = 'test2';
+ addBucketItem('field', selectedOption1);
+ addBucketItem('field2', selectedOption2);
+ component.onOptionsChange([{ value: selectedOption1 }], 'field');
+ component.onOptionsChange([{ value: selectedOption2 }], 'field2');
+ fixture.detectChanges();
+ expect(getDisplayValue()).toBe(`${component.tabbedFacet.facets['field'].label}_LABEL: ${selectedOption1} ${component.tabbedFacet.facets['field2'].label}_LABEL: ${selectedOption2}`);
+ });
+
+ it('should update search query and display value when apply btn is clicked', () => {
+ spyOn(component.menuTrigger, 'closeMenu').and.callThrough();
+ spyOn(component, 'updateDisplayValue').and.callThrough();
+ spyOn(searchFacetService, 'updateSelectedBuckets').and.callThrough();
+ openFacet();
+ const applyButton = fixture.debugElement.query(By.css('#apply-filter-button'));
+ applyButton.triggerEventHandler('click', {});
+ expect(queryBuilder.update).toHaveBeenCalled();
+ expect(component.menuTrigger.closeMenu).toHaveBeenCalled();
+ expect(component.updateDisplayValue).toHaveBeenCalled();
+ expect(searchFacetService.updateSelectedBuckets).toHaveBeenCalled();
+ });
+
+ it('should update search query and display value when cancel btn is clicked', () => {
+ spyOn(component.menuTrigger, 'closeMenu').and.callThrough();
+ spyOn(component, 'updateDisplayValue').and.callThrough();
+ openFacet();
+ const applyButton = fixture.debugElement.query(By.css('#cancel-filter-button'));
+ applyButton.triggerEventHandler('click', {});
+ expect(queryBuilder.update).toHaveBeenCalled();
+ expect(component.menuTrigger.closeMenu).toHaveBeenCalled();
+ expect(component.updateDisplayValue).toHaveBeenCalled();
+ });
+});
diff --git a/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.ts b/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.ts
new file mode 100644
index 0000000000..58e8768aba
--- /dev/null
+++ b/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.ts
@@ -0,0 +1,174 @@
+/*!
+ * @license
+ * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, ElementRef, Inject, Input, OnChanges, OnInit, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core';
+import { ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';
+import { MatMenuTrigger } from '@angular/material/menu';
+import { TabbedFacetField } from '../../../models/tabbed-facet-field.interface';
+import { Subject } from 'rxjs';
+import { SearchQueryBuilderService } from '../../../services/search-query-builder.service';
+import { SEARCH_QUERY_SERVICE_TOKEN } from '../../../search-query-service.token';
+import { FacetWidget } from '../../../models/facet-widget.interface';
+import { TranslationService } from '@alfresco/adf-core';
+import { SearchFacetFiltersService } from '../../../services/search-facet-filters.service';
+import { AutocompleteOption } from '../../../models/autocomplete-option.interface';
+
+@Component({
+ selector: 'adf-search-facet-chip-tabbed',
+ templateUrl: './search-facet-chip-tabbed.component.html',
+ styleUrls: ['./search-facet-chip-tabbed.component.scss'],
+ encapsulation: ViewEncapsulation.None
+})
+export class SearchFacetChipTabbedComponent implements OnInit, OnChanges, FacetWidget {
+ @Input()
+ tabbedFacet: TabbedFacetField;
+
+ @ViewChild('menuContainer', { static: false })
+ menuContainer: ElementRef;
+
+ @ViewChild('menuTrigger', { static: false })
+ menuTrigger: MatMenuTrigger;
+
+ private resetSubject$ = new Subject();
+
+ displayValue$ = new Subject();
+ reset$ = this.resetSubject$.asObservable();
+ focusTrap: ConfigurableFocusTrap;
+ chipIcon = 'keyboard_arrow_down';
+ autocompleteOptions = {};
+ selectedOptions = {};
+ isPopulated = false;
+
+ constructor(@Inject(SEARCH_QUERY_SERVICE_TOKEN) private queryBuilder: SearchQueryBuilderService,
+ private translationService: TranslationService,
+ private searchFacetFiltersService: SearchFacetFiltersService,
+ private focusTrapFactory: ConfigurableFocusTrapFactory) {
+ }
+
+ ngOnInit() {
+ this.tabbedFacet.fields.forEach((field) => {
+ Object.defineProperty(this.selectedOptions, field, {
+ value: [],
+ writable: true
+ });
+ });
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes.tabbedFacet) {
+ this.isPopulated = this.tabbedFacet.fields.some((field) => this.tabbedFacet.facets[field]?.buckets.items.length > 0);
+ this.tabbedFacet.fields.forEach((field) => {
+ const options: AutocompleteOption[] = this.tabbedFacet.facets[field].buckets.items.map((item) => ({ value: item.display }));
+ Object.defineProperty(this.autocompleteOptions, field, {
+ value: options,
+ writable: true
+ });
+ });
+ }
+ }
+
+ onMenuOpen() {
+ if (this.menuContainer && !this.focusTrap) {
+ this.focusTrap = this.focusTrapFactory.create(this.menuContainer.nativeElement);
+ }
+ this.chipIcon = 'keyboard_arrow_up';
+ }
+
+ onClosed() {
+ this.focusTrap.destroy();
+ this.focusTrap = null;
+ this.chipIcon = 'keyboard_arrow_down';
+ }
+
+ onRemove() {
+ this.reset();
+ this.menuTrigger.closeMenu();
+ }
+
+ onApply() {
+ this.submitValues();
+ this.menuTrigger.closeMenu();
+ }
+
+ onEnterKeydown() {
+ if (this.isPopulated) {
+ if (!this.menuTrigger.menuOpen) {
+ this.menuTrigger.openMenu();
+ } else {
+ this.menuTrigger.closeMenu();
+ }
+ }
+ }
+
+ onEscKeydown() {
+ if (this.menuTrigger.menuOpen) {
+ this.menuTrigger.closeMenu();
+ }
+ }
+
+ onOptionsChange(selectedOptions: AutocompleteOption[], field: string) {
+ this.selectedOptions[field] = selectedOptions.map((selectedOption) => selectedOption.value);
+ this.isPopulated = this.tabbedFacet.fields.some((facetField) => this.selectedOptions[facetField].length > 0);
+ this.updateDisplayValue();
+ this.updateUserFacetBuckets();
+ this.queryBuilder.update();
+ }
+
+ updateDisplayValue() {
+ let displayValue = '';
+ this.tabbedFacet.fields.forEach((field) => {
+ if (this.selectedOptions[field].length > 0) {
+ const stackedOptions = this.selectedOptions[field].join(', ');
+ displayValue += `${this.translationService.instant(this.tabbedFacet.facets[field].label + '_LABEL')}: ${stackedOptions} `;
+ }
+ });
+ this.displayValue$.next(displayValue);
+ }
+
+ reset() {
+ this.resetSubject$.next();
+ this.updateUserFacetBuckets();
+ this.updateDisplayValue();
+ this.queryBuilder.update();
+ }
+
+ submitValues() {
+ this.updateUserFacetBuckets();
+ this.searchFacetFiltersService.updateSelectedBuckets();
+ this.updateDisplayValue();
+ this.queryBuilder.update();
+ }
+
+ optionComparator(option1: AutocompleteOption, option2: AutocompleteOption): boolean {
+ return option1.value.toUpperCase() === option2.value.toUpperCase();
+ }
+
+ private updateUserFacetBuckets() {
+ this.tabbedFacet.fields.forEach((field) => {
+ this.tabbedFacet.facets[field].buckets.items.forEach((item) => {
+ const matchedOption = this.selectedOptions[field].find((option) => option === item.display);
+ if (matchedOption) {
+ item.checked = true;
+ this.queryBuilder.addUserFacetBucket(field, item);
+ } else {
+ item.checked = false;
+ this.queryBuilder.removeUserFacetBucket(field, item);
+ }
+ });
+ });
+ }
+}
diff --git a/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.html b/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.html
index d9b5dd9dc6..baf944810e 100644
--- a/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.html
+++ b/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.html
@@ -3,6 +3,12 @@
+
+
+
+
diff --git a/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.scss b/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.scss
index dee5630cb7..3277cbb268 100644
--- a/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.scss
+++ b/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.scss
@@ -1,6 +1,7 @@
@use '@angular/material' as mat;
-.adf-search-filter-chip {
+.adf-search-filter-chip,
+.adf-search-filter-chip-tabbed {
&.mat-chip {
border: 2px solid transparent;
transition: border 500ms ease-in-out;
diff --git a/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.spec.ts b/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.spec.ts
index 12beb81f22..d80a78462a 100644
--- a/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.spec.ts
+++ b/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.spec.ts
@@ -75,7 +75,7 @@ describe('SearchFilterChipsComponent', () => {
{ label: 'b2', count: 1, filterQuery: 'filter2' }]) },
{ type: 'field', label: 'f2', field: 'f2', buckets: new SearchFilterList()}
];
- searchFacetFiltersService.queryBuilder.addUserFacetBucket({ label: 'f1', field: 'f1' }, searchFacetFiltersService.responseFacets[0].buckets.items[0]);
+ searchFacetFiltersService.queryBuilder.addUserFacetBucket('f1', searchFacetFiltersService.responseFacets[0].buckets.items[0]);
const serverResponseFields: any = [
{ type: 'field', label: 'f1', field: 'f1', buckets: [
@@ -125,7 +125,7 @@ describe('SearchFilterChipsComponent', () => {
{ label: 'b2', count: 1, filterQuery: 'filter2' }]) },
{ type: 'field', label: 'f2', field: 'f2', buckets: new SearchFilterList()}
];
- queryBuilder.addUserFacetBucket({ label: 'f1', field: 'f1' }, searchFacetFiltersService.responseFacets[0].buckets.items[0]);
+ queryBuilder.addUserFacetBucket('f1', searchFacetFiltersService.responseFacets[0].buckets.items[0]);
const serverResponseFields: any = [
{ type: 'field', label: 'f1', field: 'f1', buckets: [
@@ -174,7 +174,7 @@ describe('SearchFilterChipsComponent', () => {
{ label: 'b2', count: 1, filterQuery: 'filter2' }]) },
{ type: 'field', label: 'f2', field: 'f2', buckets: new SearchFilterList() }
];
- queryBuilder.addUserFacetBucket({ label: 'f1', field: 'f1' }, searchFacetFiltersService.responseFacets[0].buckets.items[0]);
+ queryBuilder.addUserFacetBucket('f1', searchFacetFiltersService.responseFacets[0].buckets.items[0]);
const data = {
list: {
context: {}
diff --git a/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.ts b/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.ts
index 00ecad7673..b3052d8cb8 100644
--- a/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.ts
+++ b/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.ts
@@ -19,6 +19,8 @@ import { Component, Inject, Input, ViewEncapsulation } from '@angular/core';
import { SearchFacetFiltersService } from '../../services/search-facet-filters.service';
import { SEARCH_QUERY_SERVICE_TOKEN } from '../../search-query-service.token';
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
+import { Subject } from 'rxjs';
+import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'adf-search-filter-chips',
@@ -27,13 +29,27 @@ import { SearchQueryBuilderService } from '../../services/search-query-builder.s
encapsulation: ViewEncapsulation.None
})
export class SearchFilterChipsComponent {
+ private onDestroy$ = new Subject();
+
/** Toggles whether to show or not the context facet filters. */
@Input()
showContextFacets: boolean = true;
+ facetChipTabbedId = '';
+
constructor(
@Inject(SEARCH_QUERY_SERVICE_TOKEN)
public queryBuilder: SearchQueryBuilderService,
public facetFiltersService: SearchFacetFiltersService) {}
+ ngOnInit() {
+ this.queryBuilder.executed.asObservable()
+ .pipe(takeUntil(this.onDestroy$))
+ .subscribe(() => this.facetChipTabbedId = 'search-fact-chip-tabbed-' + this.facetFiltersService.tabbedFacet?.fields.join('-'));
+ }
+
+ ngOnDestroy() {
+ this.onDestroy$.next();
+ this.onDestroy$.complete();
+ }
}
diff --git a/lib/content-services/src/lib/search/components/search-filter/search-filter.component.spec.ts b/lib/content-services/src/lib/search/components/search-filter/search-filter.component.spec.ts
index 3980876bd1..8a3fc9291f 100644
--- a/lib/content-services/src/lib/search/components/search-filter/search-filter.component.spec.ts
+++ b/lib/content-services/src/lib/search/components/search-filter/search-filter.component.spec.ts
@@ -94,7 +94,7 @@ describe('SearchFilterComponent', () => {
{ label: 'b2', count: 1, filterQuery: 'filter2' }]) },
{ type: 'field', label: 'f2', field: 'f2', buckets: new SearchFilterList([]) }
];
- queryBuilder.addUserFacetBucket({ label: 'f1', field: 'f1' }, searchFacetFiltersService.responseFacets[0].buckets.items[0]);
+ queryBuilder.addUserFacetBucket('f1', searchFacetFiltersService.responseFacets[0].buckets.items[0]);
const serverResponseFields: any = [
{ type: 'field', label: 'f1', field: 'f1', buckets: [
@@ -138,7 +138,7 @@ describe('SearchFilterComponent', () => {
{ label: 'b2', count: 1, filterQuery: 'filter2' }]) },
{ type: 'field', label: 'f2', field: 'f2', buckets: new SearchFilterList([]) }
];
- searchFacetFiltersService.queryBuilder.addUserFacetBucket({ label: 'f1', field: 'f1' }, searchFacetFiltersService.responseFacets[0].buckets.items[0]);
+ searchFacetFiltersService.queryBuilder.addUserFacetBucket('f1', searchFacetFiltersService.responseFacets[0].buckets.items[0]);
const serverResponseFields: any = [
{ type: 'field', label: 'f1', field: 'f1', buckets: [
@@ -182,7 +182,7 @@ describe('SearchFilterComponent', () => {
{ label: 'b2', count: 1, filterQuery: 'filter2' }]) },
{ type: 'field', label: 'f2', field: 'f2', buckets: new SearchFilterList() }
];
- searchFacetFiltersService.queryBuilder.addUserFacetBucket({ label: 'f1', field: 'f1' }, searchFacetFiltersService.responseFacets[0].buckets.items[0]);
+ searchFacetFiltersService.queryBuilder.addUserFacetBucket('f1', searchFacetFiltersService.responseFacets[0].buckets.items[0]);
const data = {
list: {
context: {}
diff --git a/lib/content-services/src/lib/search/models/tabbed-facet-field.interface.ts b/lib/content-services/src/lib/search/models/tabbed-facet-field.interface.ts
new file mode 100644
index 0000000000..52a3391f0c
--- /dev/null
+++ b/lib/content-services/src/lib/search/models/tabbed-facet-field.interface.ts
@@ -0,0 +1,29 @@
+/*!
+ * @license
+ * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { FacetField } from './facet-field.interface';
+
+export interface TabbedFacetField {
+ /* array of fields that tabbed facet will consist of */
+ fields: string[];
+ /* label to display for tabbed facet */
+ label: string;
+ /* facets to populate tabbed facet tabs */
+ facets: {
+ [propName: string]: FacetField;
+ };
+}
diff --git a/lib/content-services/src/lib/search/public-api.ts b/lib/content-services/src/lib/search/public-api.ts
index 52176d8ff1..571797de6d 100644
--- a/lib/content-services/src/lib/search/public-api.ts
+++ b/lib/content-services/src/lib/search/public-api.ts
@@ -27,6 +27,7 @@ export * from './models/search-configuration.interface';
export * from './services/search-query-builder.service';
export * from './models/search-range.interface';
export * from './models/search-form.interface';
+export * from './models/tabbed-facet-field.interface';
export * from './search-query-service.token';
export * from './services/search-header-query-builder.service';
@@ -67,5 +68,6 @@ export * from './components/search-filter-tabbed/search-filter-tabbed.component'
export * from './components/reset-search.directive';
export * from './components/search-chip-autocomplete-input/search-chip-autocomplete-input.component';
export * from './components/search-filter-autocomplete-chips/search-filter-autocomplete-chips.component';
+export * from './components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component';
export * from './search.module';
diff --git a/lib/content-services/src/lib/search/search.module.ts b/lib/content-services/src/lib/search/search.module.ts
index 4a90815dd7..af3a9628c7 100644
--- a/lib/content-services/src/lib/search/search.module.ts
+++ b/lib/content-services/src/lib/search/search.module.ts
@@ -56,6 +56,7 @@ import { SearchFilterTabbedComponent } from './components/search-filter-tabbed/s
import { SearchDateRangeAdvancedComponent } from './components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component';
import { SearchDateRangeAdvancedTabbedComponent } from './components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component';
import { SearchFilterTabDirective } from './components/search-filter-tabbed/search-filter-tab.directive';
+import { SearchFacetChipTabbedComponent } from './components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component';
@NgModule({
imports: [
@@ -98,7 +99,8 @@ import { SearchFilterTabDirective } from './components/search-filter-tabbed/sear
SearchFilterTabbedComponent,
SearchDateRangeAdvancedComponent,
SearchDateRangeAdvancedTabbedComponent,
- SearchFilterTabDirective
+ SearchFilterTabDirective,
+ SearchFacetChipTabbedComponent
],
exports: [
SearchComponent,
@@ -126,7 +128,8 @@ import { SearchFilterTabDirective } from './components/search-filter-tabbed/sear
SearchLogicalFilterComponent,
SearchFilterTabbedComponent,
SearchDateRangeAdvancedComponent,
- ResetSearchDirective
+ ResetSearchDirective,
+ SearchFacetChipTabbedComponent
],
providers: [
{ provide: SEARCH_QUERY_SERVICE_TOKEN, useExisting: SearchQueryBuilderService }
diff --git a/lib/content-services/src/lib/search/services/base-query-builder.service.ts b/lib/content-services/src/lib/search/services/base-query-builder.service.ts
index 38f1576464..c0446f98a0 100644
--- a/lib/content-services/src/lib/search/services/base-query-builder.service.ts
+++ b/lib/content-services/src/lib/search/services/base-query-builder.service.ts
@@ -183,14 +183,14 @@ export abstract class BaseQueryBuilderService {
* @param field The target field
* @param bucket Bucket to add
*/
- addUserFacetBucket(field: FacetField, bucket: FacetFieldBucket) {
- if (field && field.field && bucket) {
- const buckets = this.userFacetBuckets[field.field] || [];
+ addUserFacetBucket(field: string, bucket: FacetFieldBucket) {
+ if (field && bucket) {
+ const buckets = this.userFacetBuckets[field] || [];
const existing = buckets.find((facetBucket) => facetBucket.label === bucket.label);
if (!existing) {
buckets.push(bucket);
}
- this.userFacetBuckets[field.field] = buckets;
+ this.userFacetBuckets[field] = buckets;
}
}
@@ -210,10 +210,10 @@ export abstract class BaseQueryBuilderService {
* @param field The target field
* @param bucket Bucket to remove
*/
- removeUserFacetBucket(field: FacetField, bucket: FacetFieldBucket) {
- if (field && field.field && bucket) {
- const buckets = this.userFacetBuckets[field.field] || [];
- this.userFacetBuckets[field.field] = buckets
+ removeUserFacetBucket(field: string, bucket: FacetFieldBucket) {
+ if (field && bucket) {
+ const buckets = this.userFacetBuckets[field] || [];
+ this.userFacetBuckets[field] = buckets
.filter((facetBucket) => facetBucket.label !== bucket.label);
}
}
diff --git a/lib/content-services/src/lib/search/services/search-facet-filters.service.spec.ts b/lib/content-services/src/lib/search/services/search-facet-filters.service.spec.ts
index c04abefdd9..d67529f0a8 100644
--- a/lib/content-services/src/lib/search/services/search-facet-filters.service.spec.ts
+++ b/lib/content-services/src/lib/search/services/search-facet-filters.service.spec.ts
@@ -497,7 +497,7 @@ describe('SearchFacetFiltersService', () => {
}]
}
];
- let data = {
+ const data = {
list: {
context: {
facets: fields
@@ -513,6 +513,55 @@ describe('SearchFacetFiltersService', () => {
expect(searchFacetFiltersService.responseFacets.length).toEqual(2);
});
+ it('should extract creator and modifier facets and create tabbed facet for them', () => {
+ searchFacetFiltersService.responseFacets = null;
+ queryBuilder.config = {
+ categories: [],
+ facetFields: { fields: [
+ { label: 'creator', field: 'creator' },
+ { label: 'modifier', field: 'modifier' }
+ ]},
+ facetQueries: {
+ queries: []
+ }
+ };
+
+ const serverResponseFields: any = [
+ {
+ type: 'field',
+ label: 'creator',
+ buckets: [
+ { label: 'b1', metrics: [{value: {count: 10}}] },
+ { label: 'b2', metrics: [{value: {count: 1}}] }
+ ]
+ },
+ {
+ type: 'field',
+ label: 'modifier',
+ buckets: [
+ { label: 'c1', metrics: [{value: {count: 10}}] },
+ { label: 'c2', metrics: [{value: {count: 1}}] }
+ ]
+ }
+ ];
+ const data = {
+ list: {
+ context: {
+ facets: serverResponseFields
+ }
+ }
+ };
+
+ searchFacetFiltersService.onDataLoaded(data);
+ expect(searchFacetFiltersService.responseFacets.length).toEqual(0);
+ expect(searchFacetFiltersService.tabbedFacet.fields).toEqual(['creator', 'modifier']);
+ expect(searchFacetFiltersService.tabbedFacet.label).toEqual('SEARCH.FILTER.PEOPLE');
+ expect(searchFacetFiltersService.tabbedFacet.facets['creator'].buckets.items[0].label).toEqual('b1');
+ expect(searchFacetFiltersService.tabbedFacet.facets['creator'].buckets.items[1].label).toEqual('b2');
+ expect(searchFacetFiltersService.tabbedFacet.facets['modifier'].buckets.items[0].label).toEqual('c1');
+ expect(searchFacetFiltersService.tabbedFacet.facets['modifier'].buckets.items[1].label).toEqual('c2');
+ });
+
describe('Bucket sorting', () => {
let data;
diff --git a/lib/content-services/src/lib/search/services/search-facet-filters.service.ts b/lib/content-services/src/lib/search/services/search-facet-filters.service.ts
index 56b965f0c4..eadb313632 100644
--- a/lib/content-services/src/lib/search/services/search-facet-filters.service.ts
+++ b/lib/content-services/src/lib/search/services/search-facet-filters.service.ts
@@ -27,6 +27,7 @@ import { GenericBucket, GenericFacetResponse, ResultSetContext, ResultSetPaging
import { SearchFilterList } from '../models/search-filter-list.model';
import { FacetFieldBucket } from '../models/facet-field-bucket.interface';
import { CategoryService } from '../../category/services/category.service';
+import { TabbedFacetField } from '../models/tabbed-facet-field.interface';
export interface SelectedBucket {
field: FacetField;
@@ -45,6 +46,8 @@ export class SearchFacetFiltersService implements OnDestroy {
* the newly received items are added to the responseFacets.
*/
responseFacets: FacetField[] = null;
+ /* tabbed facet incorporating creator and modifier facets */
+ tabbedFacet: TabbedFacetField = null;
/** shows the facet chips */
selectedBuckets: SelectedBucket[] = [];
@@ -95,17 +98,18 @@ export class SearchFacetFiltersService implements OnDestroy {
this.parseFacetIntervals(context);
this.parseFacetQueries(context);
this.sortFacets();
+ this.parseTabbedFacetField();
}
private parseFacetItems(context: ResultSetContext, configFacetFields: FacetField[], itemType: string) {
- configFacetFields.forEach((field) => {
- const responseField = this.findFacet(context, itemType, field.label);
- const responseBuckets = this.getResponseBuckets(responseField, field)
- .filter(this.getFilterByMinCount(field.mincount));
- this.sortFacetBuckets(responseBuckets, field.settings?.bucketSortBy, field.settings?.bucketSortDirection ?? FacetBucketSortDirection.ASCENDING);
- const alreadyExistingField = this.findResponseFacet(itemType, field.label);
+ configFacetFields.forEach((facetField) => {
+ const responseField = this.findFacet(context, itemType, facetField.label);
+ const responseBuckets = this.getResponseBuckets(responseField, facetField)
+ .filter(this.getFilterByMinCount(facetField.mincount));
+ this.sortFacetBuckets(responseBuckets, facetField.settings?.bucketSortBy, facetField.settings?.bucketSortDirection ?? FacetBucketSortDirection.ASCENDING);
+ const alreadyExistingField = this.findResponseFacet(itemType, facetField.label);
- if (field.field === 'cm:categories'){
+ if (facetField.field === 'cm:categories'){
this.loadCategoryNames(responseBuckets);
}
@@ -115,18 +119,18 @@ export class SearchFacetFiltersService implements OnDestroy {
this.updateExistingBuckets(responseField, responseBuckets, alreadyExistingField, alreadyExistingBuckets);
} else if (responseField) {
if (responseBuckets.length > 0) {
- const bucketList = new SearchFilterList(responseBuckets, field.pageSize);
+ const bucketList = new SearchFilterList(responseBuckets, facetField.pageSize);
bucketList.filter = this.getBucketFilterFunction(bucketList);
if (!this.responseFacets) {
this.responseFacets = [];
}
this.responseFacets.push({
- ...field,
+ ...facetField,
type: responseField.type || itemType,
- label: field.label,
- pageSize: field.pageSize | DEFAULT_PAGE_SIZE,
- currentPageSize: field.pageSize | DEFAULT_PAGE_SIZE,
+ label: facetField.label,
+ pageSize: facetField.pageSize | DEFAULT_PAGE_SIZE,
+ currentPageSize: facetField.pageSize | DEFAULT_PAGE_SIZE,
buckets: bucketList
});
}
@@ -134,6 +138,33 @@ export class SearchFacetFiltersService implements OnDestroy {
});
}
+ private parseTabbedFacetField() {
+ if (this.responseFacets) {
+ const fields = this.responseFacets.reduce((acc, facet) => `${acc},${facet.field}`, '');
+ const tabbedFacetField: TabbedFacetField = {
+ fields: ['creator', 'modifier'],
+ label: 'SEARCH.FILTER.PEOPLE',
+ facets: {}
+ };
+ this.extractCreatorAndModifier(tabbedFacetField, fields);
+ }
+ }
+
+ private extractCreatorAndModifier(tabbedFacet: TabbedFacetField, fields: string) {
+ if (fields.includes('creator') && fields.includes('modifier')) {
+ for (let i = this.responseFacets.length - 1; i >= 0; i--) {
+ if (this.responseFacets[i].field === 'creator' || this.responseFacets[i].field === 'modifier') {
+ const removedFacet = this.responseFacets.splice(i, 1)[0];
+ Object.defineProperty(tabbedFacet.facets, removedFacet.field, {
+ value: removedFacet,
+ writable: true
+ });
+ }
+ }
+ this.tabbedFacet = tabbedFacet;
+ }
+ }
+
private parseFacetFields(context: ResultSetContext) {
const configFacetFields = this.queryBuilder.config.facetFields && this.queryBuilder.config.facetFields.fields || [];
this.parseFacetItems(context, configFacetFields, 'field');
@@ -191,7 +222,6 @@ export class SearchFacetFiltersService implements OnDestroy {
}
}
});
-
}
private sortFacets() {
@@ -358,10 +388,10 @@ export class SearchFacetFiltersService implements OnDestroy {
});
}
- unselectFacetBucket(field: FacetField, bucket: FacetFieldBucket) {
+ unselectFacetBucket(facetField: FacetField, bucket: FacetFieldBucket) {
if (bucket) {
bucket.checked = false;
- this.queryBuilder.removeUserFacetBucket(field, bucket);
+ this.queryBuilder.removeUserFacetBucket(facetField.field, bucket);
this.updateSelectedBuckets();
this.queryBuilder.update();
}
@@ -371,12 +401,14 @@ export class SearchFacetFiltersService implements OnDestroy {
updateSelectedBuckets() {
if (this.responseFacets) {
this.selectedBuckets = [];
- for (const field of this.responseFacets) {
- if (field.buckets) {
+ let facetFields = this.tabbedFacet === null ? [] : Object.keys(this.tabbedFacet?.fields).map(field => this.tabbedFacet.facets[field]);
+ facetFields = [...facetFields, ...this.responseFacets];
+ for (const facetField of facetFields) {
+ if (facetField?.buckets) {
this.selectedBuckets.push(
- ...this.queryBuilder.getUserFacetBuckets(field.field)
+ ...this.queryBuilder.getUserFacetBuckets(facetField.field)
.filter((bucket) => bucket.checked)
- .map((bucket) => ({field, bucket}))
+ .map((bucket) => ({field: facetField, bucket}))
);
}
}
@@ -391,11 +423,11 @@ export class SearchFacetFiltersService implements OnDestroy {
}
resetAllSelectedBuckets() {
- this.responseFacets.forEach((field) => {
- if (field && field.buckets) {
- for (const bucket of field.buckets.items) {
+ this.responseFacets.forEach((facetField) => {
+ if (facetField?.buckets) {
+ for (const bucket of facetField.buckets.items) {
bucket.checked = false;
- this.queryBuilder.removeUserFacetBucket(field, bucket);
+ this.queryBuilder.removeUserFacetBucket(facetField.field, bucket);
}
this.updateSelectedBuckets();
}
@@ -411,6 +443,7 @@ export class SearchFacetFiltersService implements OnDestroy {
reset() {
this.responseFacets = [];
this.selectedBuckets = [];
+ this.tabbedFacet = null;
this.queryBuilder.resetToDefaults();
this.queryBuilder.update();
}
diff --git a/lib/content-services/src/lib/search/services/search-query-builder.service.spec.ts b/lib/content-services/src/lib/search/services/search-query-builder.service.spec.ts
index 88af97aa1c..2bde2193db 100644
--- a/lib/content-services/src/lib/search/services/search-query-builder.service.spec.ts
+++ b/lib/content-services/src/lib/search/services/search-query-builder.service.spec.ts
@@ -637,10 +637,10 @@ describe('SearchQueryBuilder', () => {
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
- builder.addUserFacetBucket(field1, field1buckets[0]);
- builder.addUserFacetBucket(field1, field1buckets[1]);
- builder.addUserFacetBucket(field2, field2buckets[0]);
- builder.addUserFacetBucket(field2, field2buckets[1]);
+ builder.addUserFacetBucket(field1.field, field1buckets[0]);
+ builder.addUserFacetBucket(field1.field, field1buckets[1]);
+ builder.addUserFacetBucket(field2.field, field2buckets[0]);
+ builder.addUserFacetBucket(field2.field, field2buckets[1]);
const compiledQuery = builder.buildQuery();
const expectedResult = '(f1-q1 OR f1-q2) AND (f2-q1 OR f2-q2)';