ontotext-reusable-ui-components
Version:
Unified reusable UI components designed for the Ontotext ecosystem.
1,573 lines • 78 kB
JavaScript
import { EventEmitter, Component, Input, Output, ViewChild, isDevMode, Directive, ElementRef, Renderer2, NgModule, KeyValueDiffers, ViewContainerRef, ComponentFactoryResolver, ApplicationRef, Injector, HostListener, forwardRef, ViewEncapsulation, Inject } from '@angular/core';
import { of, interval, ReplaySubject } from 'rxjs';
import { CommonModule } from '@angular/common';
import { FormControl, ReactiveFormsModule, FormsModule, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { startWith, map, debounce } from 'rxjs/operators';
import { MatAutocompleteTrigger, MatAutocompleteModule } from '@angular/material/autocomplete';
import { ENTER, COMMA } from '@angular/cdk/keycodes';
import { MatInputModule } from '@angular/material/input';
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatOptionModule, MAT_DATE_FORMATS } from '@angular/material/core';
import { MatSelectModule, MatSelect } from '@angular/material/select';
import { MatChipsModule } from '@angular/material/chips';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { TranslocoModule, TRANSLOCO_CONFIG, TranslocoService } from '@ngneat/transloco';
import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
import { CdkTableModule } from '@angular/cdk/table';
import { MatTableModule } from '@angular/material/table';
import { MatSortModule } from '@angular/material/sort';
import { MatPaginatorModule } from '@angular/material/paginator';
import { OnDestroyMixin, untilComponentDestroyed } from '@w11k/ngx-componentdestroyed';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatFormFieldModule } from '@angular/material/form-field';
import { DateRange, MatDatepickerModule } from '@angular/material/datepicker';
import moment from 'moment';
import { TemplatePortal, DomPortalOutlet } from '@angular/cdk/portal';
import { MatMomentDateModule } from '@angular/material-moment-adapter';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSliderModule } from '@angular/material/slider';
class OntoSearchFieldComponent {
constructor() {
/**
* Emits selected search phrases on search button press.
*/
this.onSearch = new EventEmitter();
/**
* On key press event emitter.
*/
this.onKeyPress = new EventEmitter();
}
ngOnInit() {
this.states = this.autocompleteData || of([]);
}
onSearchEvent($event) {
this.onSearch.emit($event);
}
onKeyPressEvent($event) {
this.onKeyPress.emit($event);
}
}
OntoSearchFieldComponent.decorators = [
{ type: Component, args: [{
selector: 'onto-search-field',
template: "<app-search\n [states]=\"states\"\n [autocompleteOptionTemplate]=\"customTemplate || defaultTemplate\"\n [preselectedStatesList]=\"selectedList || []\"\n [enableInnerAutocompleteFiltration]=\"enableInnerAutocompleteFiltration\"\n [searchResultMappingFunction]=\"searchResultMappingFunction\"\n (onSearch)=\"onSearchEvent($event)\"\n (onKeyPress)=\"onKeyPressEvent($event)\"></app-search>\n\n <ng-template #defaultTemplate let-item>\n <span [attr.appCypressData]=\"'state-label-' + item.index\" class=\"state-label\">{{item.state.label}}</span>\n <span [attr.appCypressData]=\"'state-type-' + item.index\" class=\"state-type\">{{item.state.type}}</span> <br/>\n <span [attr.appCypressData]=\"'state-labels-' + item.index\" class=\"state-labels\"><small>{{item.state.labels}}</small></span>\n</ng-template>\n",
styles: [".state-type{float:right}.state-labels{line-height:1em;white-space:normal}::ng-deep .state-suggestion{background-color:#add8e6!important;color:#000!important}"]
},] }
];
OntoSearchFieldComponent.propDecorators = {
customTemplate: [{ type: Input }],
selectedList: [{ type: Input }],
autocompleteData: [{ type: Input }],
enableInnerAutocompleteFiltration: [{ type: Input }],
searchResultMappingFunction: [{ type: Input }],
onSearch: [{ type: Output }],
onKeyPress: [{ type: Output }]
};
/**
* Configuration of the search input and mat-chip integration.
*/
class SearchFieldConfiguration {
}
/**
* Determines whether a selected phrase can be selected
*/
SearchFieldConfiguration.selectable = true;
/**
* Determines whether a selected phrase can be deleted from list
*/
SearchFieldConfiguration.removable = true;
/**
* A list of characters that can be used as a phrase separator
*/
SearchFieldConfiguration.separatorKeysCodes = [ENTER, COMMA];
class SearchComponent {
constructor() {
/**
* On search event emitter.
*/
this.onSearch = new EventEmitter();
/**
* On key press emitter.
*/
this.onKeyPress = new EventEmitter();
this.stateCtrl = new FormControl();
this.selectable = SearchFieldConfiguration.selectable;
this.removable = SearchFieldConfiguration.removable;
this.separatorKeysCodes = SearchFieldConfiguration.separatorKeysCodes;
this.defaultSearchResultMappingFunction = (data) => data;
}
ngOnInit() {
this.states.subscribe((states) => {
this._states = states;
});
this.subscribeAutocompleteFilter();
this.currentTemplate = this.autocompleteOptionTemplate;
this.searchResultMappingFunction = this.searchResultMappingFunction || this.defaultSearchResultMappingFunction;
}
ngOnChanges(changes) {
var _a;
if ((_a = changes === null || changes === void 0 ? void 0 : changes.preselectedStatesList) === null || _a === void 0 ? void 0 : _a.currentValue) {
this.statesList = new Set([...this.preselectedStatesList]);
}
}
subscribeAutocompleteFilter() {
const enableAutocomplete = this.enableInnerAutocompleteFiltration || false;
if (enableAutocomplete) {
this.filteredStates = this.stateCtrl.valueChanges
.pipe(startWith(''), map((state) => state ? this.filterStates(state) : []));
}
else {
this.filteredStates = this.states;
}
}
filterStates(value) {
const filterValue = typeof value === 'string' ? value.toLowerCase() : value;
return this._states.filter((state) => state.label.toLowerCase().indexOf(filterValue) === 0);
}
add(event) {
const input = event.input;
const value = (event.value || '').trim();
if (value) {
this.statesList.add(value);
}
if (input) {
input.value = '';
}
this.stateCtrl.setValue(null);
this.autocomplete.closePanel();
}
remove(state) {
this.statesList.delete(state);
this.stateCtrl.setValue(null);
}
selected(event) {
this.statesList.add(event.option.value);
this.statesInput.nativeElement.value = '';
this.stateCtrl.setValue(null);
}
search() {
this.onSearch.emit([...this.statesList].map((state) => {
if (state.label) {
return this.searchResultMappingFunction(state);
}
return state;
}));
}
onInputChange(value) {
this.onKeyPress.emit(value);
}
}
SearchComponent.decorators = [
{ type: Component, args: [{
selector: 'app-search',
template: "<div class=\"search-field-component\" appCypressData=\"search-field-component\">\n <div class=\"selection-list\">\n <mat-form-field class=\"state-selection-list\">\n <mat-chip-list #selectionList aria-label=\"Search selection\" appCypressData=\"search-field-selection\">\n <mat-chip\n *ngFor=\"let state of statesList; let index = index\"\n [selectable]=\"selectable\"\n [removable]=\"removable\"\n (removed)=\"remove(state)\"\n [ngClass]=\"state.label ? 'state-suggestion' : 'state-free-text'\"\n [attr.appCypressData]=\"'state-selection-' + index\">\n {{state.label || state}}\n <mat-icon matChipRemove *ngIf=\"removable\" [attr.appCypressData]=\"'state-cancel-' + index\">cancel</mat-icon>\n </mat-chip>\n <input\n placeholder=\"{{'ONTO_SEARCH.SEARCH_PLACEHOLDER' | transloco}}\"\n #statesInput\n [formControl]=\"stateCtrl\"\n [matAutocomplete]=\"auto\"\n [matChipInputFor]=\"selectionList\"\n [matChipInputSeparatorKeyCodes]=\"separatorKeysCodes\"\n (matChipInputTokenEnd)=\"add($event)\"\n (input)=\"onInputChange($event.target.value)\"\n appCypressData=\"search-field-input\">\n </mat-chip-list>\n <mat-autocomplete #auto=\"matAutocomplete\" (optionSelected)=\"selected($event)\">\n <mat-option class=\"state-option\" *ngFor=\"let state of filteredStates | async; let index = index\" [value]=\"state\">\n <ng-container *ngTemplateOutlet=\"currentTemplate; context: {$implicit: {state: state, index: index}}\">\n </ng-container>\n </mat-option>\n </mat-autocomplete>\n </mat-form-field>\n </div>\n <button class=\"search-field-button\" mat-raised-button (click)=\"search()\"\n appCypressData=\"search-field-button\">{{'ONTO_SEARCH.BUTTON.SEARCH' | transloco}}</button>\n</div>\n",
styles: [".search-field-component{align-items:center;box-sizing:border-box;display:flex;flex-direction:row;height:100%;place-content:center space-between;width:100%}.search-field-component .selection-list{box-sizing:border-box;flex:1 1 100%;max-width:100%}.search-field-component .state-selection-list{display:inherit}button.search-field-button{margin-left:1em}.state-option{height:auto;line-height:1.5em;padding:1em 0;word-wrap:break-word}"]
},] }
];
SearchComponent.propDecorators = {
states: [{ type: Input }],
preselectedStatesList: [{ type: Input }],
autocompleteOptionTemplate: [{ type: Input }],
enableInnerAutocompleteFiltration: [{ type: Input }],
searchResultMappingFunction: [{ type: Input }],
onSearch: [{ type: Output }],
onKeyPress: [{ type: Output }],
statesInput: [{ type: ViewChild, args: ['statesInput',] }],
autocomplete: [{ type: ViewChild, args: [MatAutocompleteTrigger,] }]
};
var ONTO_SEARCH = {
BUTTON: {
SEARCH: "Search"
},
SEARCH_PLACEHOLDER: "Search..."
};
var en = {
ONTO_SEARCH: ONTO_SEARCH
};
class CypressDataDirective {
constructor(el, renderer) {
this.el = el;
this.renderer = renderer;
if (!isDevMode()) {
renderer.removeAttribute(el.nativeElement, 'appCypressData');
}
}
}
CypressDataDirective.decorators = [
{ type: Directive, args: [{
selector: '[appCypressData]',
},] }
];
CypressDataDirective.ctorParameters = () => [
{ type: ElementRef },
{ type: Renderer2 }
];
class DirectivesModule {
}
DirectivesModule.decorators = [
{ type: NgModule, args: [{
imports: [],
declarations: [
CypressDataDirective,
],
exports: [
CypressDataDirective,
],
},] }
];
const translocoConfiguration = {
availableLangs: ['en'],
fallbackLang: ['en'],
defaultLang: 'en',
prodMode: true,
reRenderOnLangChange: true,
missingHandler: {
useFallbackTranslation: true,
logMissingKey: true,
},
};
const ɵ0 = translocoConfiguration;
class OntoSearchFieldModule {
constructor(translocoService) {
this.translocoService = translocoService;
this.translocoService.setTranslation(en, 'en');
}
}
OntoSearchFieldModule.decorators = [
{ type: NgModule, args: [{
declarations: [OntoSearchFieldComponent, SearchComponent],
exports: [
OntoSearchFieldComponent,
],
imports: [
CommonModule,
ReactiveFormsModule,
MatAutocompleteModule,
MatInputModule,
FormsModule,
BrowserAnimationsModule,
MatOptionModule,
MatSelectModule,
MatChipsModule,
MatIconModule,
MatButtonModule,
TranslocoModule,
DirectivesModule,
],
providers: [
{
provide: TRANSLOCO_CONFIG,
useValue: ɵ0,
},
],
},] }
];
OntoSearchFieldModule.ctorParameters = () => [
{ type: TranslocoService }
];
class OntoSearchResultsComponent {
constructor() {
/**
* Fired whenever there is change in sorting column or direction.
* Sends a {@link Sort} object
*/
this.sortData = new EventEmitter();
this.initialSort = true;
}
ngOnChanges(changes) {
if (changes.config && changes.config.currentValue) {
if (this.initialSort) {
this.sortColumnConfigurations();
this.initialSort = false;
}
this.setDisplayColumns([...this.getVisibleColumnNames()]);
}
}
reorderColumns(event) {
moveItemInArray(this.config.columnConfigurations, this.findColumnConfigIndexByName(this.displayColumns[event.previousIndex]), this.findColumnConfigIndexByName(this.displayColumns[event.currentIndex]));
this.setDisplayColumns(this.getVisibleColumnNames());
}
findColumnConfigIndexByName(name) {
return this.config.columnConfigurations.findIndex((columnConfig) => columnConfig.name === name);
}
/**
* Sorts the column configurations by permanent columns first
*/
sortColumnConfigurations() {
const permanentCC = this.getFilteredColumnConfigs((column) => column.permanent);
const dynamicCC = this.getFilteredColumnConfigs((column) => !column.permanent);
this.config.columnConfigurations = [...permanentCC, ...dynamicCC];
}
setDisplayColumns(columnNamesArray) {
this.displayColumns = columnNamesArray;
}
getFilteredColumnConfigs(filterPredicate) {
return this.config.columnConfigurations.filter(filterPredicate);
}
getVisibleColumnNames() {
return this.getFilteredColumnConfigs((column) => column.permanent || !column.hidden)
.map((column) => column.name);
}
}
OntoSearchResultsComponent.decorators = [
{ type: Component, args: [{
selector: 'onto-search-results',
template: "<div class=\"table-component\" [ngClass]=\"componentClass\" appCypressData=\"table-component\">\n <table *ngIf=\"config\" cdk-table [dataSource]=\"datasource\"\n matSort (matSortChange)=\"sortData.next($event)\"\n [matSortDisabled]=\"!config.enableSort\" [matSortDisableClear]=\"true\"\n [matSortActive]=\"sort?.active\" [matSortDirection]=\"sort?.direction\"\n class=\"onto-table onto-table-fixed-layout\" appCypressData=\"onto-table\"\n cdkDropList cdkDropListLockAxis=\"x\" cdkDropListOrientation=\"horizontal\"\n (cdkDropListDropped)=\"reorderColumns($event)\">\n <ng-container *ngFor=\"let columnConfig of config.columnConfigurations; let i=index;\"\n cdkColumnDef=\"{{columnConfig.name}}\">\n <th cdk-header-cell id=\"{{columnConfig.name}}\"\n class=\"onto-table-header-cell {{'onto-table-column-' + columnConfig.name}}\"\n *cdkHeaderCellDef appCypressData=\"onto-table-header-cell\"\n mat-sort-header [disabled]=\"!columnConfig.enableSort\"\n cdkDrag cdkDragLockAxis=\"x\">\n <div>{{columnConfig.label}}</div>\n </th>\n <td cdk-cell class=\"onto-table-cell {{'onto-table-column-' + columnConfig.name}}\"\n *cdkCellDef=\"let row\" appCypressData=\"onto-table-cell\">\n <div>\n <ng-container *ngIf=\"columnConfig.dataFunction; else cellTemplate\">\n {{columnConfig.dataFunction(row)}}\n </ng-container>\n <ng-template #cellTemplate [ngTemplateOutlet]=\"columnConfig.dataTemplate\"\n [ngTemplateOutletContext]=\"{$implicit: row}\"></ng-template>\n </div>\n </td>\n <ng-container *ngIf=\"config.showFooter\">\n <td cdk-footer-cell\n class=\"onto-table-footer-cell {{'onto-table-column-' + columnConfig.name}}\"\n *cdkFooterCellDef appCypressData=\"onto-table-footer-cell\">\n <div>{{columnConfig.footerFunction(datasource, columnConfig)}}</div>\n </td>\n </ng-container>\n </ng-container>\n\n <tr cdk-header-row class=\"onto-table-header-row\" appCypressData=\"onto-table-header-row\"\n *cdkHeaderRowDef=\"displayColumns\"></tr>\n <tr cdk-row class=\"onto-table-row\" appCypressData=\"onto-table-row\"\n *cdkRowDef=\"let row; columns: displayColumns\"></tr>\n <ng-container *ngIf=\"config.showFooter\">\n <tr cdk-footer-row class=\"onto-table-footer-row\" appCypressData=\"onto-table-footer-row\"\n *cdkFooterRowDef=\"displayColumns\"></tr>\n </ng-container>\n </table>\n</div>\n",
styles: [":host{width:100%}.onto-table-fixed-layout{table-layout:fixed}.onto-table{background:#fff;border-spacing:0;display:table;font-family:Roboto,sans-serif;width:100%}.onto-table-footer-row,.onto-table-header-row,.onto-table-row{align-items:center;border-style:solid;border-width:0 0 1px;box-sizing:border-box;display:flex}.onto-table-header-row{height:56px}.onto-table-footer-row,.onto-table-row{min-height:48px}.onto-table-cell,.onto-table-footer-cell,.onto-table-header-cell{align-items:center;border-bottom-color:#000;display:flex;flex:1;font-size:16px;font-weight:400;height:100%;min-height:inherit;overflow:hidden;padding:0;word-wrap:break-word}.onto-table-cell div,.onto-table-footer-cell div,.onto-table-header-cell div{padding:0}.onto-table-cell div:first-of-type,.onto-table-footer-cell div:first-of-type,.onto-table-header-cell div:first-of-type{padding-left:24px}.onto-table-cell div:last-of-type,.onto-table-footer-cell div:last-of-type,.onto-table-header-cell div:last-of-type{padding-right:24px}.onto-table-header-cell{text-align:left}.onto-table-cell,.onto-table-footer-cell{font-size:14px}.cdk-drag-preview{box-sizing:border-box;position:relative}.cdk-drag-preview:after{border:1px solid rgba(0,0,0,.4);border-radius:4px;bottom:0;box-shadow:0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12);content:\"\";left:5px;position:absolute;right:5px;top:0}.cdk-drag-placeholder{color:transparent;position:relative;transition:transform .25s cubic-bezier(0,0,.2,1)}.cdk-drag-placeholder:after{background:rgba(0,0,0,.1);border:1px dashed rgba(0,0,0,.4);border-radius:4px;bottom:0;content:\"\";left:5px;position:absolute;right:5px;top:0}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}"]
},] }
];
OntoSearchResultsComponent.propDecorators = {
datasource: [{ type: Input }],
config: [{ type: Input }],
sort: [{ type: Input }],
componentClass: [{ type: Input }],
sortData: [{ type: Output }]
};
class OntoSearchResultsModule {
}
OntoSearchResultsModule.decorators = [
{ type: NgModule, args: [{
declarations: [OntoSearchResultsComponent],
exports: [OntoSearchResultsComponent],
imports: [
CdkTableModule,
CommonModule,
DirectivesModule,
MatTableModule,
MatSortModule,
DragDropModule
],
},] }
];
class OntoSearchPaginatorComponent {
constructor(keyValueDiffer) {
this.keyValueDiffer = keyValueDiffer;
/**
* Fires when there is a change to page or page size.
* Emits an {@link PaginatorData} object
*/
this.pageChanged = new EventEmitter();
// Default and initial values
this.length = 0;
this.pageSize = 10;
this.pageSizeOptions = [5, 10, 20];
this.pageIndex = 0;
}
/**
* Takes care of change detection in pageOptions properties
*/
ngDoCheck() {
if (this.paginatorDataDiff) {
const changes = this.paginatorDataDiff.diff(this.paginatorData);
if (changes) {
this.configurationChanged(changes);
}
}
}
/**
* Monitors changes to inputs and sets corresponding internal properties
* @param changes - input changes
*/
ngOnChanges(changes) {
if (changes.paginatorData && changes.paginatorData.currentValue) {
this.paginatorDataDiff = this.keyValueDiffer.find(this.paginatorData).create();
this.setInternalPaginatorDataProperties(this.paginatorData);
}
if (changes.pageOptions && changes.pageOptions.currentValue) {
this.pageSizeOptions = changes.pageOptions.currentValue;
}
}
onPageChanged(pageEvent) {
const pageData = {
pageIndex: pageEvent.pageIndex,
pageSize: pageEvent.pageSize,
length: pageEvent.length
};
this.pageChanged.next(pageData);
}
/**
* Applies changes from pageOption to internal properties
* @param changes - contains changes of pageOption properties
*/
configurationChanged(changes) {
changes.forEachChangedItem((change) => {
this.setClassProperty(change.key, change.currentValue);
});
}
setInternalPaginatorDataProperties(paginatorData) {
Object.keys(paginatorData).forEach((key) => {
this.setClassProperty(key, paginatorData[key]);
});
}
setClassProperty(key, value) {
this[key] = value;
}
}
OntoSearchPaginatorComponent.decorators = [
{ type: Component, args: [{
selector: 'onto-search-paginator',
template: "<div class=\"paginator-component\" appCypressData=\"paginator-component\">\n <mat-paginator [length]=\"length\"\n [pageSize]=\"pageSize\"\n [pageSizeOptions]=\"pageSizeOptions\"\n [pageIndex]=\"pageIndex\"\n [showFirstLastButtons]=\"true\"\n (page)=\"onPageChanged($event)\" appCypressData=\"onto-paginator\"></mat-paginator>\n</div>\n",
styles: [":host{width:100%}"]
},] }
];
OntoSearchPaginatorComponent.ctorParameters = () => [
{ type: KeyValueDiffers }
];
OntoSearchPaginatorComponent.propDecorators = {
paginatorData: [{ type: Input }],
pageOptions: [{ type: Input }],
pageChanged: [{ type: Output }]
};
class OntoSearchPaginatorModule {
}
OntoSearchPaginatorModule.decorators = [
{ type: NgModule, args: [{
declarations: [OntoSearchPaginatorComponent],
exports: [OntoSearchPaginatorComponent],
imports: [
CommonModule,
DirectivesModule,
MatPaginatorModule,
],
},] }
];
/**
* List of supported facet types
*/
var SearchFacetType;
(function (SearchFacetType) {
SearchFacetType[SearchFacetType["CHECKBOX"] = 0] = "CHECKBOX";
SearchFacetType[SearchFacetType["DATE_RANGE"] = 1] = "DATE_RANGE";
SearchFacetType[SearchFacetType["TOGGLE"] = 2] = "TOGGLE";
SearchFacetType[SearchFacetType["RANGE"] = 3] = "RANGE";
SearchFacetType[SearchFacetType["DROPDOWN_MULTI_SELECT"] = 4] = "DROPDOWN_MULTI_SELECT";
})(SearchFacetType || (SearchFacetType = {}));
class OntoSearchFacetComponent extends OnDestroyMixin {
constructor() {
super(...arguments);
/**
* Emits selected search facets on selection change.
*/
this.onSelectionChange = new EventEmitter();
this.CHECKBOX = SearchFacetType.CHECKBOX;
this.DATEPICKER = SearchFacetType.DATE_RANGE;
this.TOGGLE = SearchFacetType.TOGGLE;
this.RANGE = SearchFacetType.RANGE;
this.DROPDOWN_MULTI_SELECT = SearchFacetType.DROPDOWN_MULTI_SELECT;
}
ngOnChanges(changes) {
if (changes.data && changes.data.currentValue) {
this.facetGroupName = this.data.facetGroupName;
this.facetGroup = this.data.facetGroupData;
this.selected = this.data.selected;
}
}
onSelectionEvent($event) {
this.onSelectionChange.emit($event);
}
}
OntoSearchFacetComponent.decorators = [
{ type: Component, args: [{
selector: 'onto-search-facet',
template: "<div *ngIf=\"type === CHECKBOX\">\n <onto-checkbox-facet\n [data]=\"data\"\n [type]=\"type\"\n [facetTitleTemplate]=\"facetTitleTemplate\"\n [facetTemplate]=\"facetTemplate\"\n (onSelectionChange)=\"onSelectionEvent($event)\">\n </onto-checkbox-facet>\n</div>\n\n<div *ngIf=\"type === DATEPICKER\">\n <onto-daterange-facet\n [data]=\"data\"\n [type]=\"type\"\n [facetTitleTemplate]=\"facetTitleTemplate\"\n [facetTemplate]=\"facetTemplate\"\n (onSelectionChange)=\"onSelectionEvent($event)\">>\n </onto-daterange-facet>\n</div>\n\n<div *ngIf=\"type === TOGGLE\">\n <onto-toggle-facet\n [data]=\"data\"\n [type]=\"type\"\n [facetTitleTemplate]=\"facetTitleTemplate\"\n [facetTemplate]=\"facetTemplate\"\n (onSelectionChange)=\"onSelectionEvent($event)\">>\n </onto-toggle-facet>\n</div>\n\n<div *ngIf=\"type === RANGE\">\n <onto-range-facet\n [data]=\"data\"\n [type]=\"type\"\n [facetTitleTemplate]=\"facetTitleTemplate\"\n [facetTemplate]=\"facetTemplate\"\n (onSelectionChange)=\"onSelectionEvent($event)\">>\n </onto-range-facet>\n</div>\n\n<div *ngIf=\"type === DROPDOWN_MULTI_SELECT\">\n <onto-dropdown-multi-select-facet\n [data]=\"data\"\n [type]=\"type\"\n [facetTitleTemplate]=\"facetTitleTemplate\"\n [facetTemplate]=\"facetTemplate\"\n (onSelectionChange)=\"onSelectionEvent($event)\">>\n </onto-dropdown-multi-select-facet>\n</div>\n",
styles: [".checkbox-facet-title{font-size:large}"]
},] }
];
OntoSearchFacetComponent.propDecorators = {
data: [{ type: Input }],
type: [{ type: Input }],
facetTemplate: [{ type: Input }],
facetTitleTemplate: [{ type: Input }],
onSelectionChange: [{ type: Output }]
};
class CheckboxFacetComponent extends OntoSearchFacetComponent {
ngOnChanges(changes) {
super.ngOnChanges(changes);
this.facetGroup.forEach((facet) => facet.selected = this.selected.includes(facet));
}
updateAllSelected(facet) {
const index = this.selected && this.selected.indexOf(facet);
if (index > -1) {
this.selected.splice(index, 1);
facet.selected = false;
}
else {
this.selected.push(facet);
facet.selected = true;
}
const count = this.selected.map((selectedItem) => selectedItem.count)
.reduce((totalCount, itemCount) => totalCount + itemCount, 0);
this.onSelectionChange.emit({ name: this.facetGroupName, selected: this.selected, count });
}
}
CheckboxFacetComponent.decorators = [
{ type: Component, args: [{
selector: 'onto-checkbox-facet',
template: "<div class=\"checkbox-facet\">\n <div appCypressData=\"checkbox-facet-title\">\n <ng-container *ngTemplateOutlet=\"facetTitleTemplate; context: {$implicit: facetGroupName}\">\n </ng-container>\n </div>\n <div class=\"checkbox-facet-list\" appCypressData=\"checkbox-facet-list\">\n <div *ngFor=\"let facet of facetGroup; let index = index\">\n <mat-checkbox\n [checked]=\"facet.selected\"\n (change)=\"updateAllSelected(facet)\"\n [attr.appCypressData]=\"'checkbox-facet-' + index\">\n <ng-container *ngTemplateOutlet=\"facetTemplate; context: {$implicit: facet}\">\n </ng-container>\n </mat-checkbox>\n </div>\n </div>\n</div>\n"
},] }
];
CheckboxFacetComponent.propDecorators = {
facetTemplate: [{ type: Input }],
facetTitleTemplate: [{ type: Input }],
type: [{ type: Input }]
};
const DATE_PATTERNS = {
YEAR: 'YYYY',
MONTH: 'MMMM YYYY',
DAY: 'MMMM D, YYYY'
};
class DateRangeFacetComponent extends OntoSearchFacetComponent {
constructor(renderer, _viewContainerRef, componentFactoryResolver, applicationRef, injector) {
super();
this.renderer = renderer;
this._viewContainerRef = _viewContainerRef;
this.componentFactoryResolver = componentFactoryResolver;
this.applicationRef = applicationRef;
this.injector = injector;
this.onSelectionChange = new EventEmitter();
this.dateRangeGroup = new FormGroup({
start: new FormControl(),
end: new FormControl()
});
this.daysMap = new Map();
this.monthsMap = new Map();
this.yearsMap = new Map();
this.buttonUnsubscribeFns = [];
}
ngOnChanges(changes) {
super.ngOnChanges(changes);
this.mapFacetDates(this.data.facetGroupData);
this.datePickerPlaceholder = this.data.placeholder;
this.setSelectedRange(this.data.selectedRange);
}
ngOnDestroy() {
super.ngOnDestroy();
this.unsubscribeBtnListeners();
}
calendarOpened() {
// Add to event loop queue to execute after angular has finished working on the dom
setTimeout(() => {
this.unsubscribeBtnListeners();
const buttons = this.getCalendarPanelButtons();
this.subscribeToBtnListeners(buttons);
this.updateDayStyles();
});
}
calendarClosed() {
this.unsubscribeBtnListeners();
if (this.dateRangeGroup.get('end').value && this.dateRangeGroup.get('start').value) {
this.data.selectedRange = this.getSearchDateFacetRange();
this.onSelectionChange.emit(this.data.selectedRange);
}
}
inputChange(picker) {
if (!picker.opened) {
this.calendarClosed();
}
}
mapFacetDates(facetGroupData) {
for (const facet of facetGroupData) {
const monthDate = moment(facet.facetData).startOf('month').toISOString();
const yearDate = moment(facet.facetData).startOf('year').toISOString();
this.daysMap.set(moment(facet.facetData).toISOString(), +facet.count);
const existingMonth = this.monthsMap.get(monthDate) || 0;
this.monthsMap.set(monthDate, existingMonth + +facet.count);
const existingYear = this.yearsMap.get(yearDate) || 0;
this.yearsMap.set(yearDate, existingYear + +facet.count);
}
}
updateDayStyles() {
if (document.querySelector('mat-month-view')) {
this.addTooltipsToCalendarCells('mat-month-view', this.daysMap, DATE_PATTERNS.DAY);
}
else if (document.querySelector('mat-multi-year-view')) {
this.addTooltipsToCalendarCells('mat-multi-year-view', this.yearsMap, DATE_PATTERNS.YEAR);
}
else if (document.querySelector('mat-year-view')) {
this.addTooltipsToCalendarCells('mat-year-view', this.monthsMap, DATE_PATTERNS.MONTH);
}
}
addTooltipsToCalendarCells(view, datesMap, dateFormat) {
const calendarCells = this.getCalendarCellsForView(view);
calendarCells.forEach((calendarCell) => {
datesMap.forEach((count, dateString) => {
const calendarCellDateString = moment(calendarCell.getAttribute('aria-label'), dateFormat);
if (calendarCellDateString.isSame(dateString)) {
this.createTooltipForCalendarCell(calendarCell, count);
}
});
});
}
/**
* Creates a portal outlet from the calendar cell div and attaches the tooltip template from view
* Applies "onto-date-range-facet-tooltip" class to calendar cell child div
*
* @param calendarCell - calendar cell to which the span is appended
* @param toolTipContext - context for the tooltip template
*/
createTooltipForCalendarCell(calendarCell, toolTipContext) {
const calendarCellContent = this.getCalendarCellContent(calendarCell);
const tooltipElements = this.getCalendarCellTooltipElements(calendarCell);
if (tooltipElements.length === 0) {
const template = new TemplatePortal(this.toolTipTemplate, this._viewContainerRef);
template.context = { $implicit: toolTipContext };
const outlet = new DomPortalOutlet(calendarCellContent, this.componentFactoryResolver, this.applicationRef, this.injector);
outlet.attachTemplatePortal(template);
calendarCellContent.classList.add('onto-date-range-facet-tooltip');
}
}
getCalendarPanelButtons() {
return document.querySelectorAll('mat-calendar .mat-calendar-body-cell, mat-calendar button, mat-calendar .mat-icon-button');
}
setSelectedRange(selectedRange) {
let momentDateRange;
if (selectedRange) {
momentDateRange = new DateRange(moment(selectedRange.selected.start), moment(selectedRange.selected.end));
}
else {
momentDateRange = new DateRange(null, null);
}
this.dateRangeGroup.setValue(momentDateRange);
this.dateRangeGroup.updateValueAndValidity();
this.data.selectedRange = selectedRange ? this.getSearchDateFacetRange() : null;
}
getSearchDateFacetRange() {
const start = this.dateRangeGroup.get('start').value;
const end = this.dateRangeGroup.get('end').value;
return {
name: this.facetGroupName,
selected: {
start: start.toDate(),
end: end.toDate()
},
count: this.getCountForRange(start, end)
};
}
getCountForRange(start, end) {
const dayKeys = [...this.daysMap.keys()];
return dayKeys.filter((day) => start.isSameOrBefore(day) && end.isSameOrAfter(day))
.map((key) => this.daysMap.get(key))
.reduce((accumulator, count) => accumulator + count, 0);
}
subscribeToBtnListeners(buttons) {
const unsubscribeFns = [];
buttons.forEach((btn) => {
const unsubscribe = this.renderer.listen(btn, 'click', () => {
this.calendarOpened();
});
unsubscribeFns.push(unsubscribe);
});
this.buttonUnsubscribeFns = [...unsubscribeFns];
}
unsubscribeBtnListeners() {
this.buttonUnsubscribeFns.forEach((unsubscribeFn) => {
unsubscribeFn();
});
this.buttonUnsubscribeFns = [];
}
getCalendarCellsForView(view) {
const elements = document.querySelectorAll('.onto-date-range-picker ' + view);
return elements.length > 0 ? elements[0].querySelectorAll('.mat-calendar-body-cell') : [];
}
getCalendarCellContent(calendarCell) {
return calendarCell.querySelectorAll('div')[0];
}
getCalendarCellTooltipElements(calendarCell) {
return calendarCell.querySelectorAll('span.onto-date-range-facet-tooltiptext');
}
}
DateRangeFacetComponent.decorators = [
{ type: Component, args: [{
selector: 'onto-daterange-facet',
template: "<div class=\"onto-date-range-facet\" appCypressData=\"onto-date-range-facet\">\n <div appCypressData=\"onto-date-range-facet-title\">\n <ng-container *ngTemplateOutlet=\"facetTitleTemplate\">\n </ng-container>\n </div>\n <div class=\"onto-date-range-facet-picker\" appCypressData=\"onto-date-range-facet-picker\">\n <mat-form-field>\n <mat-label>{{datePickerPlaceholder}}</mat-label>\n <mat-date-range-input [rangePicker]=\"picker\" [formGroup]=\"dateRangeGroup\">\n <input matInput matStartDate formControlName=\"start\" (dateChange)=\"inputChange(picker)\">\n <input matInput matEndDate formControlName=\"end\" (dateChange)=\"inputChange(picker)\">\n </mat-date-range-input>\n <mat-datepicker-toggle matSuffix [for]=\"picker\"\n appCypressData=\"onto-date-range-picker-toggle\"></mat-datepicker-toggle>\n <mat-date-range-picker panelClass=\"onto-date-range-picker\" #picker\n (closed)=\"calendarClosed()\"\n (opened)=\"calendarOpened()\"\n appCypressData=\"onto-date-range-picker\"></mat-date-range-picker>\n </mat-form-field>\n <ng-container *ngTemplateOutlet=\"facetTemplate; context: {$implicit: data.selectedRange}\">\n </ng-container>\n </div>\n</div>\n\n<ng-template #toolTipTemplate let-count>\n <span class=\"onto-date-range-facet-tooltiptext\">{{count}}</span>\n</ng-template>\n",
styles: ["::ng-deep .onto-date-range-facet-tooltip:not(.mat-calendar-body-selected):not(.mat-calendar-body-today){background-color:#f0f8ff;border-color:rgba(0,0,0,.38);border-style:dashed}::ng-deep .onto-date-range-facet-tooltip .onto-date-range-facet-tooltiptext{background-color:#000;border-radius:6px;bottom:120%;color:#fff;padding:8px;position:absolute;text-align:center;visibility:hidden;z-index:1}::ng-deep .onto-date-range-facet-tooltip:hover:after{border:8px solid transparent;border-top-color:#000;content:\"\";left:50%;margin-left:-8px;position:absolute;top:-25%}::ng-deep .onto-date-range-facet-tooltip:hover .onto-date-range-facet-tooltiptext{visibility:visible}"]
},] }
];
DateRangeFacetComponent.ctorParameters = () => [
{ type: Renderer2 },
{ type: ViewContainerRef },
{ type: ComponentFactoryResolver },
{ type: ApplicationRef },
{ type: Injector }
];
DateRangeFacetComponent.propDecorators = {
toolTipTemplate: [{ type: ViewChild, args: ['toolTipTemplate',] }],
data: [{ type: Input }],
onSelectionChange: [{ type: Output }]
};
class ToggleFacetComponent extends OntoSearchFacetComponent {
ngOnChanges(changes) {
var _a;
super.ngOnChanges(changes);
if ((_a = changes === null || changes === void 0 ? void 0 : changes.data) === null || _a === void 0 ? void 0 : _a.currentValue) {
this.color = this.data.color;
this.facetGroup.sort(this.compareByLabel);
}
if (this.selected.length) {
const indexOfSelectedElement = this.facetGroup.map((item) => item.label).indexOf(this.selected[0].label);
this.currentValueIndex = indexOfSelectedElement > 0 ? indexOfSelectedElement : 0;
this.currentValue = Boolean(this.currentValueIndex);
this.currentSelection = this.facetGroup[this.currentValueIndex];
if (this.facetGroup.length === 1 && indexOfSelectedElement === 0) {
this.currentValue = true;
}
}
else if (this.facetGroup.length === 1) {
this.currentSelection = this.facetGroup[0];
}
else {
this.currentSelection = null;
this.currentValue = null;
this.currentValueIndex = null;
}
}
onToggle($event) {
var _a;
let selection;
if (this.facetGroup.length === 1) {
this.facetGroup[0].selected = $event.checked;
selection = {
name: this.facetGroupName,
selected: [this.currentSelection],
count: (_a = this.currentSelection) === null || _a === void 0 ? void 0 : _a.count
};
}
else {
this.currentValueIndex = $event.checked ? 1 : 0;
this.currentSelection = this.facetGroup[this.currentValueIndex];
this.refreshSelection(this.currentValueIndex);
selection = {
name: this.facetGroupName,
selected: [this.currentSelection],
count: this.currentSelection.count
};
}
this.onSelectionChange.emit(selection);
}
refreshSelection(indexOfSelected) {
this.facetGroup.forEach((value, index) => value.selected = (index === indexOfSelected));
}
compareByLabel(item1, item2) {
if (item1.label > item2.label) {
return 1;
}
else if (item2.label > item1.label) {
return -1;
}
return 0;
}
}
ToggleFacetComponent.decorators = [
{ type: Component, args: [{
selector: 'onto-toggle-facet',
template: "<div class=\"toggle-facet\" appCypressData=\"toggle-facet\">\n <div *ngIf=\"facetTitleTemplate\">\n <ng-container [ngTemplateOutlet]=\"facetTitleTemplate\"\n [ngTemplateOutletContext]=\"{$implicit: facetGroupName}\"></ng-container>\n </div>\n <mat-slide-toggle appCypressData=\"toggle-facet-slide\" class=\"toggle-facet-slide\"\n [color]=\"color\"\n (change)=\"onToggle($event)\"\n [(ngModel)]=\"currentValue\">\n <ng-container [ngTemplateOutlet]=\"facetTemplate\"\n [ngTemplateOutletContext]=\"{$implicit: currentSelection}\">\n </ng-container>\n </mat-slide-toggle>\n</div>\n"
},] }
];
ToggleFacetComponent.propDecorators = {
data: [{ type: Input }]
};
class RangeSliderComponent {
constructor() {
this._selectionChange = new EventEmitter();
// Add debounce of 2 s, as the event is emitted on every input change (drag of slider)
this.selectionChange = this._selectionChange.pipe(debounce(() => interval(2000)));
// Size of the circle range slider button.
// It is used so that the both circles can hit each other without overlapping.
this.rangeCircleSize = 14;
}
onResize() { }
ngOnInit() {
this.initializeData();
}
ngAfterViewInit() {
this.minElement = this.minSlider.nativeElement;
this.maxElement = this.maxSlider.nativeElement;
this.init(this.selectedMin, this.selectedMax);
this.onResize = () => this.update(this.parseInt(this.maxElement.getAttribute('max')));
}
ngOnChanges(changes) {
var _a;
if ((_a = changes === null || changes === void 0 ? void 0 : changes.data) === null || _a === void 0 ? void 0 : _a.currentValue) {
this.initializeData();
this.init(this.selectedMin, this.selectedMax);
}
}
initializeData() {
var _a, _b;
this.facetData = [...this.data.facetGroupData];
this.facetData.sort((a, b) => {
try {
return this.parseInt(a.label) - this.parseInt(b.label);
}
catch (e) {
throw new Error('Facet labels must represent numbers! ' + e.message);
}
});
this.minValue = this.facetData[0].label;
this.maxValue = this.facetData[this.facetData.length - 1].label;
this.selectedMin = ((_a = this.data.selectedRange) === null || _a === void 0 ? void 0 : _a.start) || this.minValue;
this.selectedMax = ((_b = this.data.selectedRange) === null || _b === void 0 ? void 0 : _b.end) || this.maxValue;
this.sum = this.sumRange();
}
draw(splitvalue) {
const thumbsize = this.parseInt(this.slider.nativeElement.getAttribute('data-thumbsize'));
const rangewidth = this.slider.nativeElement.clientWidth;
const rangemin = this.parseInt(this.slider.nativeElement.getAttribute('data-rangemin'));
const rangemax = this.parseInt(this.slider.nativeElement.getAttribute('data-rangemax'));
this.minElement.setAttribute('max', splitvalue);
this.maxElement.setAttribute('min', splitvalue);
this.minElement.style.width = this.parseInt(thumbsize + ((splitvalue - rangemin) / (rangemax - rangemin)) * (rangewidth - (2 * thumbsize))) + 'px';
this.maxElement.style.width = this.parseInt(thumbsize + ((rangemax - splitvalue) / (rangemax - rangemin)) * (rangewidth - (2 * thumbsize))) + 'px';
this.minElement.style.left = '0px';
this.maxElement.style.left = this.parseInt(this.minElement.style.width) + 'px';
// correct for 1 off at the end of the max slider
if (this.maxElement.value > (rangemax - 1)) {
this.maxElement.setAttribute('data-value', rangemax);
}
this.maxElement.value = this.maxElement.getAttribute('data-value');
this.minElement.value = this.minElement.getAttribute('data-value');
}
init(min, max) {
if (!(this.minElement && this.maxElement)) {
return;
}
this.minElement.removeEventListener('input', () => this.update);
this.maxElement.removeEventListener('input', () => this.update);
const rangemin = this.parseInt(this.minValue);
const rangemax = this.parseInt(this.maxValue);
const selectedMin = this.parseInt(min) || rangemin;
const selectedMax = this.parseInt(max) || rangemax;
const avgvalue = (selectedMin + selectedMax) / 2;
this.minElement.setAttribute('data-value', selectedMin);
this.maxElement.setAttribute('data-value', selectedMax);
this.slider.nativeElement.setAttribute('data-rangemin', rangemin);
this.slider.nativeElement.setAttribute('data-rangemax', rangemax);
this.slider.nativeElement.setAttribute('data-thumbsize', this.rangeCircleSize);
this.slider.nativeElement.setAttribute('data-rangewidth', this.slider.nativeElement.offsetWidth);
this.draw(avgvalue);
this.minElement.addEventListener('input', () => this.update(rangemax));
this.maxElement.addEventListener('input', () => this.update(rangemax));
}
update(rangemax) {
// correction for min value calculation
let minvalue;
if (this.minElement.value > (rangemax - 1)) {
minvalue = Math.floor(this.minElement.value - 1);
}
else {
minvalue = Math.floor(this.minElement.value);
}
// correction for max value calculation
let maxvalue;
if (this.maxElement.value > (rangemax - 1)) {
maxvalue = Math.ceil(this.maxElement.value);
}
else {
maxvalue = Math.floor(this.maxElement.value);
}
this.minElement.setAttribute('data-value', minvalue);
this.maxElement.setAttribute('data-value', maxvalue);
this.selectedMin = minvalue;
this.selectedMax = maxvalue;
this.updateSelection();
this.sum = this.sumRange();
const avgvalue = Math.ceil((minvalue + maxvalue) / 2);
this.draw(avgvalue);
}
sumRange() {
let sum = 0;
this.data.facetGroupData.forEach((facet) => {
if (this.isInRange(facet)) {
sum += facet.count;
}
});
return sum;
}
onSelectionChange() {
const selection = {
name: this.data.facetGroupName,
selected: this.data.selectedRange,
count: this.sumRange()
};
this._selectionChange.emit(selection);
}
updateSelection() {
this.data.selectedRange = {
start: this.selectedMin,
end: this.selectedMax
};
this.onSelectionChange();
}
isInRange(facet) {
const value = this.parseInt(facet.label);
return value >= this.parseInt(this.selectedMin) && value <= this.parseInt(this.selectedMax);
}
parseInt(value) {
try {
return parseInt(value);
}
catch (e) {
throw new Error(`Parsing ${value} to number failed! ` + e.message);
}
}
ngOnDestroy() {
this.onResize = function () { };
this.minElement.removeEventListener('input', () => this.update);
this.maxElement.removeEventListener('input', () => this.update);
}
}
RangeSliderComponent.decorators = [
{ type: Component, args: [{
selector: 'onto-range-slider',
template: "<div class=\"range-slider-wrapper\">\n <div class=\"range-slider\">\n <div class=\"slider\" #slider>\n <input class=\"range-slider-range min-range\" type=\"range\" min=\"{{minValue}}\" max=\"{{maxValue}}\" #minSlider\n appCypressData=\"min-range\"/>\n <input class=\"range-slider-range max-range\" type=\"range\" min=\"{{minValue}}\" max=\"{{maxValue}}\" #maxSlider\n appCypressData=\"max-range\"/>\n </div>\n </div>\n <div class=\"range-values\">\n <div class=\"range-slider-value value-min\">{{minValue}}</div>\n <div class=\"range-slider-value value-max\">{{maxValue}}</div>\n </div>\n <div class=\"range-selecton\">\n <ng-container *ngTemplateOutlet=\"facetTemplate; context: {$implicit: data.selectedRange, sum: sumRange()}\">\n </ng-container>\n </div>\n</div>\n\n",
styles: [".range-slider{flex-direction:column;place-content:center space-around}.range-slider,.slider{align-items:center;cursor:pointer;display:flex;width:100%}.slider{flex-direction:row;max-height:100%;place-content:stretch center;text-align:center}.range-values{align-items:center;display:flex;flex-direction:row;place-content:space-between}.range-selecton,.range-values{margin-top:12px}.range-slider-range{-webkit-appearance:none;background:#d7dcdf;height:10px;margin:0;outline:none;padding:0;width:100%}.range-slider-range::-webkit-slider-thumb{-webkit-appearance:none;-webkit-transition:background .15s ease-in-out;appearance:none;background:#2c3e50;border-radius:50%;cursor:pointer;height:20px;transition:background .15s ease-in-out;width:20px}.range-slider-range::-webkit-slider-thumb:hover,.range-slider-range:active::-webkit-slider-thumb{background:#1abc9c}.range-slider-range::-moz-range-thumb{-moz-transition:background .15s ease-in-out;background:#2c3e50;border:0;border-radius:50%;cursor:pointer;height:20px;transition:background .15s ease-in-out;width:20px}.range-slider-range::-moz-range-thumb:hover,.range-slider-range:active::-moz-range-thumb{background:#1abc9c}.range-slider-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 3px #fff,0 0 0 6px #1abc9c}::-moz-range-track{background:#d7dcdf;border:0}input::-moz-focus-inner,input::-moz-focus-outer{border:0}"]
},] }
];
RangeSliderComponent.propDecorators = {
data: [{ type: Input }],
facetTemplate: [{ type: Input }],
slider: [{ type: ViewChild, args: ['slider',] }],
minSlider: [{ type: ViewChild, args: ['minSlider',] }],
maxSlider: [{ type: ViewChild, args: ['maxSlider',] }],
selectionChange: [{ type: Output }],
onResize: [{ type: HostListener, args: ['window:resize',] }]
};
class RangeFacetComponent extends OntoSearchFacetComponent {
}
RangeFacetComponent.decorators = [
{ type: Component, args: [{
selector: 'onto-range-facet',
template: "<div class=\"onto-range-facet\" appCypressData=\"onto-range-facet\">\n <ng-container *ngTemplateOutlet=\"facetTitleTemplate; context: {$implicit: data.facetGroupName}\">\n </ng-container>\n<onto-range-histogram *ngIf=\"data.showHistogram\" [data]=\"data\"></onto-range-histogram>\n<onto-range-slider [data]=\"data\" [facetTemplate]=\"facetTemplate\" (selectionChange)=\"onSelectionEvent($event)\"></onto-range-slider>\n</div>\n",
styles: [".onto-range-facet{width:100%}"]
},] }
];
RangeFacetComponent.propDecorators = {
data: [{ type: Input }],
facetTemplate: [{ type: Input }]
};
class RangeHistogramComponent {
ngAfterViewInit() {
this.histogramModel = this.data.histogramConfiguration;
this.init();
// An observer that monitors the resizing of the canvas element, causing it to redraw on changes.
// @ts-ignore
const observer = new ResizeObserver(() => {
this.init();
});
observer.observe(this.canvas.nativeElement);
}
ngOnChanges(changes) {
var _a;
if ((_a = changes.data) === null || _a === void 0 ? void 0 : _a.currentValue) {
this.histogramModel = this.data.histogramConfiguration;
this.init();
}
}
init() {
if (!this.canvas) {
return;
}
this.fitToContainer(this.canvas.nativeElement);
this.ctx = this.canvas.nativeElement.getContext('2d');
const data = [...this.data.facetGroupData];
data.sort((a, b) => {
return this.parseInt(a.label) - this.parseInt(b.label);
});
this.drawHistogram(this.fillEmptyValues(data));
}
drawHistogram(data) {
var _a, _b, _c;
this.ctx.clearRect(0, 0, this.canvas.nativeElement.getBoundingClientRect().width, this.canvas.nativeElement.getBoundingClientRect().height);
this.ctx.save();
const canvasWidth = this.canvas.nativeElement.getBoundingClientRect().width;
const width = canvasWidth / data.length;
const scale = ((_a = this.histogramModel) === null || _a === void 0 ? void 0 : _a.scale) || 0.5;
const baseYPos = ((_b = this.histogramModel) === null || _b === void 0 ? void 0 : _b.baseYPos) || 100;
const fillStyle = ((_c = this.histogramModel) === null || _c === void 0 ? void 0 : _c.fillStyle) || 'rgba(200, 200, 200, 0.2)';
let posX = 0;
let posY = 0;
for (let i = 0; i < data.length; i++) {
if (data[i].count > 0) {
const height = this.parseInt(data[i].count) * scale;
posX = i * width;
posY = baseYPos - height;
this.ctx.beginPath();
this.ctx.arc(posX, posY, data[i].count * scale, 0, 2 * Math.PI);
this.ctx.fillStyle = fillStyle;
this.ctx.fill();
this.ctx.restore();
}
}
this.ctx.restore();
}
fitToContainer(canvas) {
// Make it visually fill the positioned parent
canvas.style.width = '100%';
canvas.style.height = '100%';
// then set the internal size to match
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
}
compare(a, b) {
const parsedA = this.parseInt(a.label);
const parsedB = this.parseInt(b.label);
if (parsedA < parsedB) {
return -1;
}
if (parsedA > parsedB) {
return 1;
}
return 0;
}
fillEmptyValues(data) {
const empty = { label: '0', count: 0 };
const filledArray = [];
data.forEach((value, index) => {
filledArray.push(value);
if (data[index + 1]) {
let emptyValuesCount = this.parseInt(data[index + 1].label) - this.parseInt(data[index].label);
while (emptyValuesCount > 1) {
filledArray.push(empty);
emptyValuesCount--;
}
}
});
return filledArray;
}
parseInt(value) {
try {
return parseInt(value);
}
catch (e) {
throw new Error(`Parsing ${value} to number failed! ` + e.message);
}
}
}
RangeHistogramComponent.decorators = [
{ type: Component, args: [{
selector: 'onto-range-histogram',
template: "<div class=\"range-histogram\">\n <canvas id=\"range-histogram-canvas\" #rangeHistogramCanvas>\n {{\"ONTO_RANGE_FACET.NO_CANVAS_SUPPORT\" | transloco}}\n </canvas>\n</div>\n",
styles: [".range-histogram{align-items:center;display:flex;flex-direction:row;height:100px;margin-bottom:10px;place-content:center}"]
},] }
];
RangeHistogramComponent.propDecorators = {
data: [{ type: Input }],
canvas: [{ type: ViewChild, args: ['rangeHistogramCanvas',] }]
};
class RangeFacetModule {
}
RangeFacetModule.decorators = [
{ type: NgModule, args: [{
declarations: [RangeFacetComponent, RangeSliderComponent, RangeHistogramComponent],
imports: [
CommonModule,
MatSliderModule,
TranslocoModule,
DirectivesModule,
],
exports: [RangeFacetComponent]
},] }
];
class DropdownMultiSelectFacetComponent extends OntoSearchFacetComponent {
constructor() {
super(...arguments);
/** control for the selected from multi-selection */
this.selectedFormControl = new FormControl();
/** control for the MatSelect filter keyword multi-selection */
this.filteredFormControl = new FormControl();
/** list of filtered by search keyword for multi-selection */
this.filtered = new ReplaySubject(1);
}
ngOnChanges(changes) {
super.ngOnChanges(changes);
if (changes.data && changes.data.currentValue) {
this.selectedFormControl.setValue(this.selected);
this.init();
}
}
init() {
this.filtered.next(this.facetGroup.slice());
this.selectedFormControl.setValue(this.selected);
this.filteredFormControl.valueChanges
.pipe(untilComponentDestroyed(this))
.subscribe(() => {
this.filter();
});
}
filter() {
if (!Array.isArray(this.facetGroup)) {
return;
}
let search = this.filteredFormControl.value;
if (!search) {
this.filtered.next(this.facetGroup.slice());
return;
}
search = search.toLowerCase();
this.filtered.next(this.facetGroup.filter((item) => item.label.toLowerCase().indexOf(search) > -1));
}
onSelection($event) {
const selection = $event.value;
selection.forEach((item) => item.selected = true);
this.facetGroup.filter((item) => selection.indexOf(item) === -1).map((item) => item.selected = false);
this.selectedFacets = $event.value;
}
onOpenedChange(isOpen) {
if (!isOpen) {
const selection = {
name: this.facetGroupName,
selected: this.selectedFacets,
count: this.countFacetResults()
};
this.onSelectionChange.emit(selection);
}
}
countFacetResults() {
let sum = 0;
this.selectedFormControl.value.forEach((facet) => sum += facet.count);
return sum;
}
}
DropdownMultiSelectFacetComponent.decorators = [
{ type: Component, args: [{
selector: 'onto-dropdown-multi-select-facet',
template: "<div class=\"dropdown-multi-select\" appCypressData=\"dropdown-multi-select\">\n <div appCypressData=\"facet-title\">\n <ng-container *ngTemplateOutlet=\"facetTitleTemplate; context: {$implicit: facetGroupName}\">\n </ng-container>\n </div>\n <mat-form-field>\n <mat-select [formControl]=\"selectedFormControl\" multiple @.disabled (selectionChange)=\"onSelection($event)\" (openedChange)=\"onOpenedChange($event)\">\n <mat-select-trigger appCypressData=\"dropdown-multi-select\">\n <span *ngFor=\"let facet of selectedFormControl.value; let index = index\" appCypressData=\"selected-facets\">\n {{facet.label}}<span *ngIf=\"index < selectedFormControl.value.length - 1\">{{'ONTO_DROPDOWN.DELIMITER' | transloco}}</span>\n </span>\n </mat-select-trigger>\n <onto-dropdown-multi-select [formControl]=\"filteredFormControl\"></onto-dropdown-multi-select>\n <mat-option *ngFor=\"let item of filtered | async\" [value]=\"item\">\n <ng-container *ngTemplateOutlet=\"facetTemplate; context: {$implicit: item}\">\n </ng-container>\n </mat-option>\n </mat-select>\n </mat-form-field>\n</div>\n",
styles: [".dropdown-multi-select,.dropdown-multi-select mat-form-field{width:100%}"]
},] }
];
class DropdownMultiSelectComponent extends OnDestroyMixin {
constructor(matSelect) {
super();
this.matSelect = matSelect;
this.onChange = () => { };
this.onTouched = () => { };
this.change = new EventEmitter();
this.ENTER = 'Enter';
}
/** Current search value */
get searchValue() {
return this.value;
}
ngOnInit() {
this.matSelect.panelClass = 'mat-select-search-panel';
this.matSelect.openedChange
.pipe(untilComponentDestroyed(this))
.subscribe((opened) => {
this.options = this.matSelect.options;
opened ? this.focus() : this.reset();
});
// Hack to prevent auto focus and Space or Enter interaction with mat options.
this.matSelect.typeaheadDebounceInterval = 31536000;
this.init();
}
initMultiSelectedValues() {
if (this.matSelect.multiple) {
this.previousSelectedValues = this.matSelect.options
.filter((option) => option.selected)
.map((option) => option.value);
}
}
/**
* Handles the key down event with MatSelect.
* Do not handles ENTER key press
* @param {KeyboardEvent} event
*/
handleKeydown(event) {
if (event.key === this.ENTER) {
event.stopPropagation();
}
}
writeValue(value) {
const valueChanged = value !== this.value;
if (valueChanged) {
this.value = value;
this.change.emit(value);
}
}
onInputChange(value) {
const valueChanged = value !== this.value;
if (valueChanged) {
this.initMultiSelectedValues();
this.value = value;
this.onChange(value);
this.change.emit(value);
}
}
onBlur(value) {
this.writeValue(value);
this.onTouched();
}
registerOnChange(fn) {
this.onChange = fn;
}
registerOnTouched(fn) {
this.onTouched = fn;
}
/**
* Focuses the search input field
*/
focus() {
if (!this.searchSelectInput) {
return;
}
// Needed to trigger focus after angular digest. Otherwise focus may be lost
setTimeout(() => {
this.searchSelectInput.nativeElement.focus();
}, 0);
}
/**
* Resets the current search value
* @param {boolean} focus whether to focus after resetting
*/
reset(focus) {
if (!this.searchSelectInput) {
return;
}
this.searchSelectInput.nativeElement.value = '';
this.onInputChange('');
if (focus) {
this.focus();
}
}
/**
* Initializes handling <mat-select [multiple]="true">
* Note: to improve this code, mat-select should be extended to allow disabling resetting the selection while filtering.
*/
init() {
// if <mat-select [multiple]="true">
// store previously selected values and restore them when they are deselected
// because the option is not available while we are currently filtering
this.matSelect.valueChange
.pipe(untilComponentDestroyed(this))
.subscribe((values) => {
if (this.matSelect.multiple) {
let restoreSelectedValues = false;
if (this.value && this.value.length && Array.isArray(this.previousSelected)) {
if (!Array.isArray(values)) {
values = [];
}
const optionValues = this.matSelect.options.map((option) => option.value);
this.previousSelected.forEach((previousValue) => {
if (values.indexOf(previousValue) === -1 && optionValues.indexOf(previousValue) === -1) {
// if a value that was selected before is deselected and not found in the options, it was deselected
// due to the filtering, so we restore it.
values.push(previousValue);
restoreSelectedValues = true;
}
});
}
if (restoreSelectedValues) {
this.matSelect._onChange(values);
}
this.previousSelected = values;
}
});
}
}
DropdownMultiSelectComponent.decorators = [
{ type: Component, args: [{
selector: 'onto-dropdown-multi-select',
template: "<div class=\"mat-select-search-inner\">\n <input class=\"mat-select-search-input\" appCypressData=\"mat-select-search-input\"\n #searchSelectInput\n (keydown)=\"handleKeydown($event)\"\n (input)=\"onInputChange($event.target.value)\"\n (blur)=\"onBlur($event.target.value)\"\n placeholder=\"{{'ONTO_DROPDOWN.SEARCH_PLACEHOLDER' | transloco}}\"\n autocomplete=\"off\"/>\n <button mat-button *ngIf=\"searchValue\"\n mat-icon-button\n aria-label=\"Clear\"\n (click)=\"reset(true)\"\n class=\"mat-select-search-clear-button\">\n <mat-icon>close</mat-icon>\n </button>\n</div>\n\n<div *ngIf=\"searchValue && options?.length === 0\"\n class=\"mat-select-search-no-found\" appCypressData=\"mat-select-search-no-found\">\n {{'ONTO_DROPDOWN.NOT_FOUND' | transloco}}\n</div>\n",
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DropdownMultiSelectComponent),
multi: true
}
],
encapsulation: ViewEncapsulation.None,
styles: [".mat-select-search-inner{background:#fff;border-bottom:1px solid #3f51b5;margin-right:16px;position:absolute;top:0;width:100%;z-index:100}input{border:0;font-family:Roboto,Helvetica Neue,sans-serif}input:focus{outline:none}.mat-select-search-panel{max-height:350px;min-width:100%!important;transform:none!important}.mat-select-search-input{box-sizing:border-box;padding:16px 36px 16px 16px;width:calc(100% - 42px)!important}.mat-select-search-no-found{font-family:Roboto,Helvetica Neue,sans-serif;padding:16px}.mat-select-search-clear-button{position:absolute;right:0;top:4px}.cdk-overlay-pane{animation:fadeIn .2s ease-in!important;margin-top:50px!important;opacity:1!important;padding-top:50px!important;transform:translateY(-15px)!important}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.mat-icon-button{margin-bottom:8px!important}"]
},] }
];
DropdownMultiSelectComponent.ctorParameters = () => [
{ type: MatSelect, decorators: [{ type: Inject, args: [MatSelect,] }] }
];
DropdownMultiSelectComponent.propDecorators = {
searchSelectInput: [{ type: ViewChild, args: ['searchSelectInput', { static: false, read: ElementRef },] }]
};
var ONTO_RANGE_FACET = {
SELECTED: "Selected: ",
NO_CANVAS_SUPPORT: "Your browser does not support canvas.",
SELECTED_MIN: "Selected min:",
SELECTED_MAX: "Selected max:"
};
var ONTO_DROPDOWN = {
SEARCH_PLACEHOLDER: "Search...",
NOT_FOUND: "No match found!",
DELIMITER: ","
};
var en$1 = {
ONTO_RANGE_FACET: ONTO_RANGE_FACET,
ONTO_DROPDOWN: ONTO_DROPDOWN
};
const CUSTOM_MAT_DATE_FORMATS = {
parse: {
dateInput: ['l', 'LL', 'DD/MM/YYYY', 'MM/DD/YYYY'],
},
display: {
dateInput: 'L',
monthYearLabel: 'MMM YYYY',
dateA11yLabel: 'LL',
monthYearA11yLabel: 'MMMM YYYY',
},
};
const translocoConfiguration$1 = {
availableLangs: ['en'],
fallbackLang: ['en'],
defaultLang: 'en',
prodMode: true,
reRenderOnLangChange: true,
missingHandler: {
useFallbackTranslation: true,
logMissingKey: true,
},
};
const ɵ0$1 = CUSTOM_MAT_DATE_FORMATS, ɵ1 = translocoConfiguration$1;
class OntoSearchFacetModule {
constructor(translocoService) {
this.translocoService = translocoService;
this.translocoService.setTranslation(en$1, 'en');
}
}
OntoSearchFacetModule.decorators = [
{ type: NgModule, args: [{
declarations: [OntoSearchFacetComponent, CheckboxFacetComponent, DateRangeFacetComponent, ToggleFacetComponent, DropdownMultiSelectFacetComponent, DropdownMultiSelectComponent],
imports: [
CommonModule,
MatCheckboxModule,
FormsModule,
DirectivesModule,
MatFormFieldModule,
MatInputModule,
MatDatepickerModule,
MatMomentDateModule,
ReactiveFormsModule,
MatSlideToggleModule,
TranslocoModule,
MatSelectModule,
MatIconModule,
MatButtonModule,
NoopAnimationsModule,
MatChipsModule,
RangeFacetModule
],
exports: [OntoSearchFacetComponent],
providers: [
{
provide: MAT_DATE_FORMATS,
useValue: ɵ0$1
},
{
provide: TRANSLOCO_CONFIG,
useValue: ɵ1,
},
],
},] }
];
OntoSearchFacetModule.ctorParameters = () => [
{ type: TranslocoService }
];
/**
* Facet group data model for Date Range Facet
*
* @param {string} facetGroupName - Used as label for the facet
* @param {SearchDateFacetModel[]} facetGroupData - An array containing facet data
* @param {SearchFacetModel[]} selected - not used in Date Range Facet
* @param {string} [placeHolder] - placeholder for date picker
* @param {SearchDateFacetRange} selectedRange - used to pass date range to facet group and is emitted when there is a selection
*/
class SearchDateFacetGroupModel {
}
/**
* Facet data model for date range OntoSearchFaced
*
* @param {number} count - the 'hit' count for the facet
* @param {Date} facetData - date object for current facet
* @param {string} label - not used in Date Range Facet
* @param {boolean} selected - not used in Date Range Facet
*/
class SearchDateFacetModel {
}
/**
* Facet group data model for Range Facet
*
* @param {string} facetGroupName - Used as label for the facet
* @param {SearchFacetModel[]} facetGroupData - An array containing facet data
* @param {SearchFacetModel[]} selected - Not used in Range facet
* @param {SelectedRange} selectedRange - used to pass range and is emitted when there is a selection
* @param {boolean} showHistogram - Flag to render a facet count histogram. By default is set to false.
* @param {HistogramModel} histogramConfiguration - Used to define histogram configuration.
*/
class SearchRangeFacetGroupModel {
constructor() {
this.showHistogram = false;
}
}
class OntoSearchColumnSelector {
constructor() {
/**
* Fired when the dropdown panel is closed.
* Sends an array {@link SelectionColumn}
*/
this.selectionChanged = new EventEmitter();
this.selectCtrl = new FormControl();
this.selection = [];
this.isSelectionChanged = false;
}
ngOnChanges(changes) {
var _a, _b, _c;
const defaultColumns = (_a = changes === null || changes === void 0 ? void 0 : changes.defaultColumns) === null || _a === void 0 ? void 0 : _a.currentValue;
const selectedColumns = (_b = changes === null || changes === void 0 ? void 0 : changes.selectedColumns) === null || _b === void 0 ? void 0 : _b.currentValue;
const columnGroups = (_c = changes === null || changes === void 0 ? void 0 : changes.columnGroups) === null || _c === void 0 ? void 0 : _c.currentValue;
if (defaultColumns && defaultColumns.length > 0 && !this.selectedColumns) {
this.setFormColumnSelection(defaultColumns);
}
if (selectedColumns) {
this.setFormColumnSelection(selectedColumns);
}
if (columnGroups === null || columnGroups === void 0 ? void 0 : columnGroups.length) {
this.columnGroups.sort(this.compareColumnsByLabel);
}
}
selectionChangedInternal($event) {
this.isSelectionChanged = true;
this.selection = $event.value;
}
onClosed() {
if (this.isSelectionChanged) {
this.isSelectionChanged = false;
this.selectionChanged.next(this.selection);
}
}
compareColumnsLabels(columnA, columnB) {
return columnA.label === columnB.label;
}
resetColumns() {
this.setFormColumnSelection(this.defaultColumns);
this.onClosed();
}
setFormColumnSelection(columns) {
this.selectCtrl.setValue([...columns]);
this.selectCtrl.updateValueAndValidity();
this.selection = columns;
this.isSelectionChanged = true;
}
compareColumnsByLabel(columnA, columnB) {
if (columnA.label > columnB.label) {
return 1;
}
else if (columnB.label > columnA.label) {
return -1;
}
return 0;
}
}
OntoSearchColumnSelector.decorators = [
{ type: Component, args: [{
selector: 'onto-search-column-selector',
template: "<div class=\"onto-column-selector-component\" appCypressData=\"onto-column-selector-component\">\n <button class=\"column-reset-button\" mat-raised-button appCypressData=\"column-reset-button\"\n color=\"primary\"\n (click)=\"resetColumns()\">{{'ONTO_COLUMN_SELECTOR.BUTTON.RESET' | transloco}}</button>\n <div class=\"column-select\">\n <mat-form-field class=\"form-field\">\n <mat-label>{{'ONTO_COLUMN_SELECTOR.COLUMN_SELECTOR_PLACEHOLDER' | transloco}}</mat-label>\n <mat-select multiple (selectionChange)=\"selectionChangedInternal($event)\"\n [formControl]=\"selectCtrl\" [compareWith]=\"compareColumnsLabels\"\n (closed)=\"onClosed()\" appCypressData=\"column-selection-dropdown\">\n <mat-optgroup *ngFor=\"let columnGroup of columnGroups, let index = index\"\n [label]=\"columnGroup.label\" [attr.appCypressData]=\"'column-group-' + index\">\n <mat-option *ngFor=\"let column of columnGroup.columns, let index = index\" [value]=\"column\"\n [attr.appCypressData]=\"'column-option-' + index\">\n {{column.label}}\n </mat-option>\n </mat-optgroup>\n </mat-select>\n </mat-form-field>\n </div>\n</div>\n",
styles: [".onto-column-selector-component{align-items:center;box-sizing:border-box;display:flex;flex-direction:row;height:100%;place-content:center flex-start;width:100%}.onto-column-selector-component .column-select{box-sizing:border-box;flex:1 1}.onto-column-selector-component .form-field{display:inherit}.onto-column-selector-component .column-reset-button{margin-right:1em}"]
},] }
];
OntoSearchColumnSelector.propDecorators = {
columnGroups: [{ type: Input }],
selectedColumns: [{ type: Input }],
defaultColumns: [{ type: Input }],
selectionChanged: [{ type: Output }]
};
var ONTO_COLUMN_SELECTOR = {
BUTTON: {
RESET: "Reset"
},
COLUMN_SELECTOR_PLACEHOLDER: "Search results columns"
};
var en$2 = {
ONTO_COLUMN_SELECTOR: ONTO_COLUMN_SELECTOR
};
const translocoConfiguration$2 = {
availableLangs: ['en'],
fallbackLang: ['en'],
defaultLang: 'en',
prodMode: true,
reRenderOnLangChange: true,
missingHandler: {
useFallbackTranslation: true,
logMissingKey: true,
},
};
const ɵ0$2 = translocoConfiguration$2;
class OntoSearchColumnSelectorModule {
constructor(translocoService) {
this.translocoService = translocoService;
this.translocoService.setTranslation(en$2, 'en');
}
}
OntoSearchColumnSelectorModule.decorators = [
{ type: NgModule, args: [{
imports: [
DirectivesModule,
MatButtonModule,
MatFormFieldModule,
MatSelectModule,
ReactiveFormsModule,
TranslocoModule,
CommonModule
],
declarations: [OntoSearchColumnSelector],
providers: [
{
provide: TRANSLOCO_CONFIG,
useValue: ɵ0$2,
},
],
exports: [
OntoSearchColumnSelector
]
},] }
];
OntoSearchColumnSelectorModule.ctorParameters = () => [
{ type: TranslocoService }
];
/*
* Public API Surface of onto-search
*/
/**
* Generated bundle index. Do not edit.
*/
export { OntoSearchColumnSelector, OntoSearchColumnSelectorModule, OntoSearchFacetComponent, OntoSearchFacetModule, OntoSearchFieldComponent, OntoSearchFieldModule, OntoSearchPaginatorComponent, OntoSearchPaginatorModule, OntoSearchResultsComponent, OntoSearchResultsModule, SearchDateFacetGroupModel, SearchDateFacetModel, SearchFacetType, SearchFieldConfiguration, SearchRangeFacetGroupModel, ɵ0, ɵ1, SearchComponent as ɵa, DirectivesModule as ɵb, CypressDataDirective as ɵc, CheckboxFacetComponent as ɵd, DateRangeFacetComponent as ɵe, ToggleFacetComponent as ɵf, DropdownMultiSelectFacetComponent as ɵg, DropdownMultiSelectComponent as ɵh, RangeFacetModule as ɵi, RangeFacetComponent as ɵj, RangeSliderComponent as ɵk, RangeHistogramComponent as ɵl };
//# sourceMappingURL=onto-search.js.map