UNPKG

ontimize-web-ngx

Version:
709 lines 137 kB
import { __decorate, __metadata } from "tslib"; import { SelectionModel } from '@angular/cdk/collections'; import { FlatTreeControl } from '@angular/cdk/tree'; import { Component, ContentChild, ElementRef, EventEmitter, forwardRef, Inject, Injector, Optional, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core'; import { Subscription } from 'rxjs'; import { BooleanInputConverter } from '../../decorators/input-converter'; import { ComponentStateServiceProvider, OntimizeServiceProvider } from '../../services/factories'; import { OTreeComponentStateService } from '../../services/state/o-tree-component-state.service'; import { Codes } from '../../util/codes'; import { FilterExpressionUtils } from '../../util/filter-expression.utils'; import { Util } from '../../util/util'; import { OFormComponent } from '../form/o-form.component'; import { AbstractOServiceComponent } from '../o-service-component.class'; import { OTreeDao } from './o-tree-dao.service'; import { OTreeDataSource } from './o-tree.datasource'; import { OTreeNodeComponent } from './tree-node/tree-node.component'; import { ServiceUtils } from '../../util/service.utils'; import { MatPaginator } from '@angular/material/paginator'; import { O_COMPONENT_STATE_SERVICE } from '../../injection-tokens'; import * as i0 from "@angular/core"; import * as i1 from "@angular/common"; import * as i2 from "../input/search-input/o-search-input.component"; import * as i3 from "@angular/flex-layout/flex"; import * as i4 from "@angular/flex-layout/extended"; import * as i5 from "@angular/material/button"; import * as i6 from "@angular/material/checkbox"; import * as i7 from "@angular/material/icon"; import * as i8 from "@angular/material/menu"; import * as i9 from "@angular/material/progress-bar"; import * as i10 from "@angular/material/paginator"; import * as i11 from "@angular/material/tree"; import * as i12 from "./header/tree-menu/o-tree-menu.component"; import * as i13 from "../../pipes/o-translate.pipe"; import * as i14 from "../form/o-form.component"; export const DEFAULT_INPUTS_O_TREE = [ 'oattr: attr', 'service', 'entity', 'columns', 'keys', 'parentKeys: parent-keys', 'queryMethod: query-method', 'insertButton: insert-button', 'refreshButton: refresh-button', 'deleteButton: delete-button', 'visibleColumns: visible-columns', 'selectAllCheckbox: select-all-checkbox', 'separator', 'parentColumn: parent-column', 'sortColumn: sort-column', 'selectAllCheckboxVisible: select-all-checkbox-visible', 'filterCaseSensitive: filter-case-sensitive', 'quickFilter: quick-filter', 'quickFilterPlaceholder: quick-filter-placeholder', 'quickFilterColumns: quick-filter-columns', 'detailMode: detail-mode', 'detailFormRoute: detail-form-route', 'showButtonsText: show-buttons-text', 'rootTitle: root-title', 'recursive', 'route' ]; export const DEFAULT_OUTPUTS_O_TREE = ['onNodeSelected', 'onNodeExpanded', 'onNodeCollapsed', 'onLoadNextLevel', 'onDataLoaded', 'onNodeClick']; export class OTreeComponent extends AbstractOServiceComponent { getTreeNodeChildren(node) { if (node.level === 0 && Util.isDefined(this.rootTitle)) { return this.rootNodes; } else if (node.childNode) { if (Util.isDefined(node.childNode.rootTitle) && !Util.isDefined(node.rootNode)) { let rootNode = { id: this.dataSource.data.length + 1, rootNode: true, label: this.translateService.get(node.childNode.rootTitle), level: node.level + 1, expandable: true, data: node.data, isLoading: false, node: node.childNode, childNode: node.childNode }; this.daoTree.flatNodeMap.set(rootNode, node); return [rootNode]; } else { return this.childQueryData(node); } } else { return this.childreNodes.filter((item) => item[this.parentKeys] === node[this.keys]); } } getRecursiveChildrenNode(node) { if (node.level === 0 && Util.isDefined(this.rootTitle)) { return this.rootNodes; } else { return this.childQueryData(node); } } getComponentFilter(existingFilter = {}) { let filter = existingFilter; if (this.recursive && this.parentColumn !== undefined) { const parentItemExpr = FilterExpressionUtils.buildExpressionFromObject(filter); const parentNotNullExpr = FilterExpressionUtils.buildExpressionIsNull(this.parentColumn); const filterExpr = FilterExpressionUtils.buildComplexExpression(parentItemExpr, parentNotNullExpr, FilterExpressionUtils.OP_AND); filter = {}; filter[FilterExpressionUtils.FILTER_EXPRESSION_KEY] = filterExpr; } return super.getComponentFilter(filter); } onLoadMore(event, node) { event.stopPropagation(); event.preventDefault(); const parentNode = this.getParentNode(node); if (!parentNode || parentNode.isLoading) return; parentNode.isLoading = true; parentNode.offset = Math.min((parentNode.offset ?? 0) + parentNode.childNode.queryRows, parentNode.totalQueryRecordsNumber); parentNode.hasMore = parentNode.offset + parentNode.childNode.queryRows < parentNode.totalQueryRecordsNumber; this.getChildren(parentNode).subscribe({ next: (res) => { if (res.isSuccessful()) { const newData = res.data || []; this.dataSource.updateTree(parentNode, newData, true); } }, error: (err) => { this.dialogService.error('ERROR', 'MESSAGES.ERROR_QUERY'); parentNode.isLoading = false; } }); } isLastChildAndHasMore(node) { let parent = this.daoTree.flatNodeMap.get(node); if (!parent?.hasMore) return false; const siblings = this.treeControl.getDescendants(parent) .filter(child => child.level === node.level); if (!siblings || siblings.length === 0) return false; const lastSibling = siblings[siblings.length - 1]; return node.id === lastSibling.id; } set nodeTemplate(value) { if (value != null) { this.leafNodeTemplate = value; this.parentNodeTemplate = value; } } get showTreeMenuButton() { const staticOpt = this.selectAllCheckbox; return staticOpt; } constructor(injector, elRef, form) { super(injector, elRef, form); this.injector = injector; this.getLevel = (node) => node.level; this.isExpandable = (node) => node.expandable; this.getChildren = (node) => { if (this.recursive) { return this.getRecursiveChildrenNode(node); } else { return this.getTreeNodeChildren(node); } }; this.hasChild = (_, _nodeData) => _nodeData.expandable; this.hasNoContent = (_, _nodeData) => _nodeData.label === ''; this.hasLoadMore = (node) => this.getLogicalLevel(node) > 0 && this.treeControl.isExpanded(node) && node.node.pageable; this.refreshButton = true; this.deleteButton = false; this.showButtonsText = false; this.separator = Codes.HYPHEN_SEPARATOR; this.selectAllCheckboxVisible = false; this.selectAllCheckbox = false; this.recursive = false; this._quickFilter = false; this.paginationControls = false; this.childreNodes = []; this.nodesArray = []; this.ancestors = []; this.onNodeSelected = new EventEmitter(); this.onNodeExpanded = new EventEmitter(); this.onNodeCollapsed = new EventEmitter(); this.onLoadNextLevel = new EventEmitter(); this.onNodeClick = new EventEmitter(); this.rootNodes = []; this.visibleColumnsArray = []; this.enabledDeleteButton = false; this.subscription = new Subscription(); this.transformer = (node, level, parentNode) => { const nodeChildren = this.childreNodes.filter((item) => item[this.parentColumn] === node[this.keys]); const flatNode = { 'id': this.getNodeId(node, parentNode), 'label': this.getItemText(node), 'level': level, 'node': this, childNode: this.treeNode, 'expandable': Util.isDefined(this.treeNode) || !!nodeChildren?.length || this.recursive, 'data': node, 'isLoading': false, 'route': this.route, 'offset': this.parentComponent?.queryRows ?? 0 }; this.daoTree.flatNodeMap.set(flatNode, parentNode); nodeChildren.forEach(node => this.transformer(node, level + 1)); return flatNode; }; this.daoTree = this.injector.get(OTreeDao); } ngOnInit() { this.setTreeControl(); this.initialize(); this.initializeParams(); this.subscription.add(this.selection.changed.subscribe(() => (this.enabledDeleteButton = !this.selection.isEmpty()))); this.permissions = this.permissionsService.getTreePermissions(this.oattr, this.actRoute); this.actionsPermissions = this.getActionsPermissions(this.permissions); this.setButtonPermissions(this.actionsPermissions); } initialize() { super.initialize(); this.initializeDao(); } initializeDao() { if (this.staticData) { this.queryOnBind = false; this.queryOnInit = false; this.setDataArray(this.staticData); } else { this.configureService(); } } initializeParams() { if (!this.visibleColumns) { this.visibleColumns = this.columns; } if (this.state) { this.state.queryRecordOffset = 0; this.currentPage = this.state.currentPage ?? 0; } if (!Util.isDefined(this.quickFilterColumns)) { this.quickFilterColumns = this.visibleColumns; } this.parseSortColumn(); } get state() { return this.componentStateService.state; } ngAfterViewInit() { this.visibleColumnsArray = Util.parseArray(this.visibleColumns, true); this.quickFilterColArray = Util.parseArray(this.quickFilterColumns, true); this.setDatasource(); this.afterViewInit(); this.registerQuickFilter(this.searchInputComponent); if (this.queryOnInit) { this.queryData(); } this.manageCustomPermissions(this.actionsPermissions, '[o-tree-button]'); } ngOnDestroy() { this.destroy(); this.subscription.unsubscribe(); } registerTreeNode(oTreeNode) { } checkboxClicked(event) { event.stopPropagation(); } leafNodeClicked(event, node) { this.nodeClicked(node, event); } parentNodeClicked(event, node) { this.nodeClicked(node, event); } nodeClicked(node, event) { event.stopPropagation(); event.preventDefault(); this.onNodeClick.emit(node); this.selectedNode = node; if (this.detailMode !== Codes.DETAIL_MODE_NONE && !this.isRootNode(node)) { node.node.viewDetail(node.data, { label: node.label }); } } isRootNode(node) { return Util.isDefined(node.rootNode) && node.rootNode; } onClickToggleButton(event, node) { event.stopPropagation(); if (this.treeControl.isExpanded(node)) { this.onNodeExpanded.emit(node); } else { this.onNodeCollapsed.emit(node); } } toggleNode(node, expand) { node.isLoading = true; if (expand && node.expandable) { const children = this.getChildren(node); if (Util.isArray(children)) { this.dataSource.updateTree(node, children, expand); } else { this.updateAsyncTree(children, node, expand); } } else { this.dataSource.updateTree(node, [], expand); } } updateAsyncTree(children, flatNode, expand) { children.subscribe((res) => { let data; if (res.isSuccessful()) { const arrData = (res.data !== undefined) ? res.data : []; data = Util.isArray(arrData) ? arrData : []; } const node = this.recursive ? flatNode.node : flatNode.childNode; flatNode.hasMore = false; flatNode.totalQueryRecordsNumber = node.pageable ? res.totalQueryRecordsNumber : data.length; const canLoadMore = this.hasLoadMore(flatNode); flatNode.hasMore = false; if (canLoadMore) { flatNode.hasMore = Util.isDefined(res.startRecordIndex) ? (node.queryRows + res.startRecordIndex < res.totalQueryRecordsNumber) : (this.queryRows < data.length); } this.dataSource.updateTree(flatNode, data, expand); }, err => { flatNode.isLoading = false; if (Util.isDefined(this.queryFallbackFunction)) { this.queryFallbackFunction(err); } else { this.oErrorDialogManager.openErrorDialog(err); console.error(err); } }); } todoLeafItemSelectionToggle(node) { this.todoItemSelectionToggle(node); } todoItemSelectionToggle(node) { this.selection.toggle(node); const descendants = this.treeControl.getDescendants(node); this.selection.isSelected(node) ? this.selection.select(...descendants) : this.selection.deselect(...descendants); descendants.every((child) => this.selection.isSelected(child)); this.onNodeSelected.emit(node.data); } descendantsAllSelected(node) { let descAllSelected = false; const descendants = this.treeControl.getDescendants(node); if (node.expandable) { if (this.treeControl.isExpanded(node) && descendants.length > 0) { descAllSelected = descendants.every((child) => this.selection.isSelected(child)); descAllSelected ? this.selection.select(node) : this.selection.deselect(node); return descAllSelected; } else { return this.selection.isSelected(node); } } else { return this.selection.isSelected(node); } } descendantsPartiallySelected(node) { let result = false; const descendants = this.treeControl.getDescendants(node); if (node.expandable) { if (descendants.length > 0) { result = descendants.some((child) => this.selection.isSelected(child)); } } return result && !this.descendantsAllSelected(node); } setTreeControl() { if (!Util.isDefined(this.treeControl)) { this.treeControl = new FlatTreeControl(this.getLevel, this.isExpandable); } } setDatasource() { if (!Util.isDefined(this.dataSource)) { this.dataSource = new OTreeDataSource(this, this.treeControl, this.injector); } } getParentNodes(node, index, tree) { let parentNode = this.daoTree.flatNodeMap.get(node); if (Util.isDefined(parentNode)) { const existingNode = tree.findIndex(x => x['id'] === parentNode['id']) > -1; if (Util.isDefined(parentNode)) { if (!existingNode) { tree.splice(index, 0, parentNode); return this.getParentNodes(parentNode, index, tree); } else { return this.getParentNodes(parentNode, index, tree); } } else { return tree; } } else { return tree; } } filterData(value, loadMore) { if (this.pageable) { const queryArgs = { offset: 0, length: this.queryRows, replace: true }; this.queryData(void 0, queryArgs); return; } let filteredTreeData = []; if (value) { for (let [nestedNode] of this.daoTree.flatNodeMap) { if (nestedNode.label.toLocaleLowerCase().indexOf(value.toLocaleLowerCase()) > -1) { filteredTreeData.push(nestedNode); } } ; let index = 0; while (index < filteredTreeData.length) { let node = filteredTreeData[index]; const parentNodes = this.getParentNodes(node, index, filteredTreeData); if (!Util.isArrayEmpty(parentNodes)) { filteredTreeData = parentNodes; } index++; } this.dataSource.data = filteredTreeData; } else { filteredTreeData = this.dataResponseArray; this.setDataArray(filteredTreeData); } if (value) { let rootNodes = filteredTreeData.filter(node => node.level == 0); this.expandNodesWithNodes(rootNodes); } } expandNodesWithNodes(treeData) { treeData.filter(node => node.expandable).forEach(node => { const descendants = this.treeControl.getDescendants(node); if (descendants.length > 0) { this.treeControl.expand(node); this.expandNodesWithNodes(descendants); } }); } getDataArray() { return this.daoTree.data; } setDataArray(data) { this.daoTree.flatNodeMap.clear(); this.daoTree.setDataArray(data); if (this.recursive) { this.childreNodes = data.filter((item) => item[this.parentColumn] != null); this.rootNodes = data.filter((item) => !Util.isDefined(item[this.parentColumn]) || item[this.parentColumn] === null); } this.rootNodes = data; let level = 0; let rootNode; if (Util.isDefined(this.rootTitle)) { level = +1; rootNode = { id: 0, label: this.translateService.get(this.rootTitle), rootNode: true, level: 0, expandable: true, data: {}, isLoading: false }; this.daoTree.flatNodeMap.set(rootNode, null); this.dataSource.data = [rootNode]; this.treeControl.expand(rootNode); } else { this.dataSource.data = this.rootNodes.map(node => this.transformer(node, level)); } } onSelectCheckboxChange(visible) { this.selectAllCheckboxVisible = visible; } sort(array) { if (this.sortColumn != null) { array.sort((a, b) => a.data[this.sortColumn].localeCompare(b.data[this.sortColumn])); array .filter((node) => !!this.treeControl.getDescendants(node)) .forEach((node) => this.sort(this.treeControl.getDescendants(node))); } } getItemText(item) { return this.visibleColumnsArray .filter((col) => item[col] != null && `${item[col]}`.length > 0) .map((col) => item[col]) .join(this.separator); } getItemKey(item) { return this.keysArray.map((col) => item[col]).join(';'); } filterByQuickFilterColumns(item, quickfilter) { const caseSensitive = this.isFilterCaseSensitive(); const regExpStr = new RegExp(Util.escapeSpecialCharacter(Util.normalizeString(quickfilter, !caseSensitive))); return this.getQuickFilterColumns().some((col) => regExpStr.test(Util.normalizeString(item.data[col] + '', !caseSensitive))); } add(e) { super.insertDetail(); } getNodeId(item, parentNode) { let id = ''; this.keysArray.forEach(key => { id += item[key]; }); if (Util.isDefined(this.parentKeys) && Util.isDefined(parentNode)) { id += parentNode.id; } return this.keys + ':' + id; } navigateToViewDetail(node) { if (Util.isDefined(node.route)) { let route = undefined; let nodeRoute = node.route; let routeArray = nodeRoute.split(Codes.ROUTE_SEPARATOR); for (let i = 0, len = routeArray.length; i < len; i++) { if (routeArray[i].startsWith(Codes.ROUTE_VARIABLE_CHAR)) { routeArray[i] = node.data[routeArray[i].substring(1)]; } } route = routeArray.join(Codes.ROUTE_SEPARATOR); if (Util.isDefined(route)) { const extras = { relativeTo: this.actRoute }; this.router.navigate([route], extras); } } } isSelectedNode(node) { return this.selectedNode == node; } onItemDetailClick(node) { this.handleItemClick(node.data); } get selection() { if (!Util.isDefined(this._selection)) { this._selection = new SelectionModel(true, [], true, (sm1, sm2) => sm1.id === sm2.id); } return this._selection; } getSelectedItems() { const selectedFlatNodes = this.selection.selected; const selectedKeys = this.getSelectedNodeKeys(selectedFlatNodes); const nodeMap = new Map(); const rootNodes = []; for (const flatNode of selectedFlatNodes) { const key = this.getFlatNodeIdentifier(flatNode); const treeNode = this.createTreeNode(flatNode); nodeMap.set(key, treeNode); const parent = this.daoTree.flatNodeMap.get(flatNode); if (!parent) { rootNodes.push(treeNode); continue; } const parentKey = this.getFlatNodeIdentifier(parent); if (!selectedKeys.has(parentKey)) { rootNodes.push(treeNode); continue; } const parentNode = this.ensureNodeInMap(parent, nodeMap); this.addChildToParent(parentNode, treeNode); if (this.shouldBeRoot(parent, selectedKeys) && !rootNodes.includes(parentNode)) { rootNodes.push(parentNode); } } return rootNodes; } parseSortColumn() { this.sortColumnArray = ServiceUtils.parseSortColumns(this.sortColumn) || []; } getQueryArguments(filter, ovrrArgs) { const queryArguments = super.getQueryArguments(filter, ovrrArgs); if (this.pageable) { if (Util.isDefined(this.sortColumnArray)) { queryArguments[6] = this.sortColumnArray; } } return queryArguments; } shouldBeRoot(parent, selectedKeys) { const grandParent = this.daoTree.flatNodeMap.get(parent); if (!grandParent) return true; const grandParentKey = this.getFlatNodeIdentifier(grandParent); return !selectedKeys.has(grandParentKey); } createTreeNode(flatNode) { return flatNode.node ? { ...flatNode.data } : { label: flatNode.label }; } getFlatNodeIdentifier(flatNode) { if (flatNode.node?.getItemKey) { return flatNode.node.getItemKey(flatNode.data); } else if (flatNode.id != null) { return flatNode.id.toString(); } return flatNode.label; } addChildToParent(parentNode, childNode) { if (!parentNode.children) { parentNode.children = []; } parentNode.children.push(childNode); } addParentToRootIfNoGrandparentSelected(parent, parentKey, selectedKeys, nodeMap, rootNodes) { const grandParent = this.daoTree.flatNodeMap.get(parent); const grandParentKey = grandParent ? this.getFlatNodeIdentifier(grandParent) : null; if (!grandParent || !selectedKeys.has(grandParentKey)) { const parentNode = nodeMap.get(parentKey); if (!rootNodes.includes(parentNode)) { rootNodes.push(parentNode); } } } ensureNodeInMap(flatNode, map) { const key = this.getFlatNodeIdentifier(flatNode); if (!map.has(key)) { map.set(key, this.createTreeNode(flatNode)); } return map.get(key); } getSelectedNodeKeys(nodes) { return new Set(nodes.map(node => this.getFlatNodeIdentifier(node))); } getSelectedFlatNodes() { return this.selection.selected; } getParentNode(node) { const currentLevel = this.getLevel(node); if (currentLevel < 1) { return null; } const startIndex = this.treeControl.dataNodes.indexOf(node) - 1; for (let i = startIndex; i >= 0; i--) { const currentNode = this.treeControl.dataNodes[i]; if (this.getLevel(currentNode) === currentLevel - 1) { return currentNode; } } return null; } reloadData(clearSelectedItems = true) { if (clearSelectedItems) { this.clearSelection(); } let queryArgs; if (this.pageable) { queryArgs = { offset: this.currentPage * this.queryRows, length: this.queryRows }; } this.queryData(void 0, queryArgs); } getLogicalLevel(node) { const hasFakeRoot = this.hasFakeRoot(); return hasFakeRoot ? node.level - 1 : node.level; } hasFakeRoot() { return !!this.rootTitle; } childQueryData(flatNode) { return this.daoTree.queryNodeChildren(flatNode, this.recursive); } } OTreeComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: OTreeComponent, deps: [{ token: i0.Injector }, { token: i0.ElementRef }, { token: forwardRef(() => OFormComponent), optional: true }], target: i0.ɵɵFactoryTarget.Component }); OTreeComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: OTreeComponent, selector: "o-tree", inputs: { oattr: ["attr", "oattr"], service: "service", entity: "entity", columns: "columns", keys: "keys", parentKeys: ["parent-keys", "parentKeys"], queryMethod: ["query-method", "queryMethod"], insertButton: ["insert-button", "insertButton"], refreshButton: ["refresh-button", "refreshButton"], deleteButton: ["delete-button", "deleteButton"], visibleColumns: ["visible-columns", "visibleColumns"], selectAllCheckbox: ["select-all-checkbox", "selectAllCheckbox"], separator: "separator", parentColumn: ["parent-column", "parentColumn"], sortColumn: ["sort-column", "sortColumn"], selectAllCheckboxVisible: ["select-all-checkbox-visible", "selectAllCheckboxVisible"], filterCaseSensitive: ["filter-case-sensitive", "filterCaseSensitive"], quickFilter: ["quick-filter", "quickFilter"], quickFilterPlaceholder: ["quick-filter-placeholder", "quickFilterPlaceholder"], quickFilterColumns: ["quick-filter-columns", "quickFilterColumns"], detailMode: ["detail-mode", "detailMode"], detailFormRoute: ["detail-form-route", "detailFormRoute"], showButtonsText: ["show-buttons-text", "showButtonsText"], rootTitle: ["root-title", "rootTitle"], recursive: "recursive", route: "route" }, outputs: { onNodeSelected: "onNodeSelected", onNodeExpanded: "onNodeExpanded", onNodeCollapsed: "onNodeCollapsed", onLoadNextLevel: "onLoadNextLevel", onDataLoaded: "onDataLoaded", onNodeClick: "onNodeClick" }, host: { properties: { "class.o-tree": "true" } }, providers: [ OTreeDao, OntimizeServiceProvider, ComponentStateServiceProvider, { provide: O_COMPONENT_STATE_SERVICE, useClass: OTreeComponentStateService }, ], queries: [{ propertyName: "leafNodeTemplate", first: true, predicate: ["leafNodeTemplate"], descendants: true, read: TemplateRef }, { propertyName: "parentNodeTemplate", first: true, predicate: ["parentNodeTemplate"], descendants: true, read: TemplateRef }, { propertyName: "nodeTemplate", first: true, predicate: ["nodeTemplate"], descendants: true, read: TemplateRef }, { propertyName: "treeNode", first: true, predicate: i0.forwardRef(function () { return OTreeNodeComponent; }) }], viewQueries: [{ propertyName: "matpaginator", first: true, predicate: MatPaginator, descendants: true }], usesInheritance: true, ngImport: i0, template: "<div fxLayout=\"column\" fxFlexFill>\n <div fxFlex>\n <div *ngIf=\"hasControls()\" fxLayout=\"row\" fxLayoutAlign=\"start center\">\n\n <div class=\"buttons\">\n <ng-container *ngIf=\"showButtonsText;else showButtonsWithoutText\">\n\n <button *ngIf=\"insertButton\" type=\"button\" mat-stroked-button aria-label=\"Insert\" [disabled]=\"!enabledInsertButton\" (click)=\"add($event)\">\n <mat-icon svgIcon=\"ontimize:add\"></mat-icon>\n <span>{{ 'INSERT' | oTranslate }}</span>\n </button>\n\n <button *ngIf=\"refreshButton\" type=\"button\" mat-stroked-button aria-label=\"Refresh\" [disabled]=\"!enabledRefreshButton\"\n (click)=\"reloadData()\">\n <mat-icon svgIcon=\"ontimize:autorenew\"></mat-icon>\n <span>{{ 'REFRESH' | oTranslate }}</span>\n </button>\n\n <button *ngIf=\"deleteButton\" type=\"button\" mat-stroked-button aria-label=\"Delete\" [disabled]=\"!enabledDeleteButton\"\n [class.disabled]=\"!enabledDeleteButton\">\n <mat-icon svgIcon=\"ontimize:delete\"></mat-icon>\n <span>{{ 'DELETE' | oTranslate }}</span>\n </button>\n\n </ng-container>\n\n <ng-template #showButtonsWithoutText>\n <button *ngIf=\"insertButton\" type=\"button\" mat-icon-button aria-label=\"Insert\" [disabled]=\"!enabledInsertButton\" (click)=\"add($event)\">\n <mat-icon svgIcon=\"ontimize:add\"></mat-icon>\n </button>\n\n <button *ngIf=\"refreshButton\" type=\"button\" mat-icon-button aria-label=\"Refresh\" [disabled]=\"!enabledRefreshButton\" (click)=\"reloadData()\">\n <mat-icon svgIcon=\"ontimize:autorenew\"></mat-icon>\n </button>\n\n <button *ngIf=\"deleteButton\" type=\"button\" mat-icon-button aria-label=\"Delete\" [disabled]=\"!enabledDeleteButton\"\n [class.disabled]=\"!enabledDeleteButton\">\n <mat-icon svgIcon=\"ontimize:delete\"></mat-icon>\n </button>\n </ng-template>\n\n <ng-content select=\"[o-tree-button]\"></ng-content>\n </div>\n <div fxFlex>\n <span fxLayoutAlign=\"center center\" class=\"title\">{{ title | oTranslate }}</span>\n </div>\n <o-search-input *ngIf=\"quickFilter\" [filter-case-sensitive]=\"filterCaseSensitive\" [show-case-sensitive-checkbox]=\"showCaseSensitiveCheckbox()\"\n [columns]=\"quickFilterColumns\" [placeholder]=\"quickFilterPlaceholder\" appearance=\"outline\" float-label=\"never\" show-menu=\"no\">\n </o-search-input>\n <button type=\"button\" *ngIf=\"showTreeMenuButton\" mat-icon-button class=\"o-tree-menu-button\" [matMenuTriggerFor]=\"treeMenu.matMenu\"\n (click)=\"$event.stopPropagation()\">\n <mat-icon svgIcon=\"ontimize:more_vert\"></mat-icon>\n </button>\n <o-tree-menu #treeMenu [select-all-checkbox]=\"selectAllCheckbox\" [select-all-checkbox-visible]=\"selectAllCheckboxVisible\"\n (onSelectCheckboxChange)=\"onSelectCheckboxChange($event)\"></o-tree-menu>\n\n </div>\n\n <mat-tree [dataSource]=\"dataSource\" [treeControl]=\"treeControl\">\n\n <!-- This is the tree node template for leaf nodes -->\n <mat-tree-node *matTreeNodeDef=\"let node\" (click)=\"leafNodeClicked($event, node)\" matTreeNodeToggle matTreeNodePadding\n [ngClass]=\"{'selected-node': isSelectedNode(node)}\">\n <!-- use a disabled button to provide padding for tree leaf -->\n <div fxLayout=\"column\">\n <div fxLayout=\"row\">\n <button mat-icon-button disabled></button>\n\n <ng-container *ngIf=\"selectAllCheckboxVisible; else simpleLeafNodeTpl\">\n <div class=\"node-wrapper\">\n <mat-checkbox *ngIf=\"selectAllCheckboxVisible; else simpleLeafNodeTpl\" [checked]=\"selection.isSelected(node)\"\n (click)=\"checkboxClicked($event)\" (change)=\"todoLeafItemSelectionToggle(node)\" (keyDown)=\"checkboxClicked($event)\">\n </mat-checkbox>\n <ng-container [ngTemplateOutlet]=\"leafNodeFulltemplate\" [ngTemplateOutletContext]=\"{$implicit:node}\">\n </ng-container>\n </div>\n </ng-container>\n <ng-template #simpleLeafNodeTpl>\n <ng-container [ngTemplateOutlet]=\"leafNodeFulltemplate\" [ngTemplateOutletContext]=\"{$implicit:node}\">\n </ng-container>\n </ng-template>\n </div>\n <div *ngIf=\"isLastChildAndHasMore(node)\">\n <button #buttonLoadMore mat-button color=\"primary\" (click)=\"onLoadMore($event, node)\">\n {{ 'O_TREE.LOAD_MORE' | oTranslate}}\n </button>\n </div>\n </div>\n\n </mat-tree-node>\n <!-- This is the tree node template for expandable nodes -->\n <mat-tree-node *matTreeNodeDef=\"let node; when: hasChild\" (click)=\"parentNodeClicked($event, node)\" matTreeNodePadding\n [ngClass]=\"{'selected-node': isSelectedNode(node)}\">\n <button mat-icon-button [attr.aria-label]=\"'toggle ' + node.label\" matTreeNodeToggle (click)=\"onClickToggleButton($event, node)\">\n <mat-icon class=\"mat-icon-rtl-mirror\">\n {{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}\n </mat-icon>\n </button>\n\n <ng-container *ngIf=\"selectAllCheckboxVisible; else simpleParentNodeTpl\">\n <div class=\"node-wrapper\">\n <mat-checkbox *ngIf=\"selectAllCheckboxVisible; else simpleParentNodeTpl\" (click)=\"checkboxClicked($event)\"\n (change)=\"todoItemSelectionToggle(node)\" [checked]=\"descendantsAllSelected(node)\" [indeterminate]=\"descendantsPartiallySelected(node)\">\n </mat-checkbox>\n <ng-container [ngTemplateOutlet]=\"parentNodeFulltemplate\" [ngTemplateOutletContext]=\"{$implicit:node}\">\n </ng-container>\n </div>\n </ng-container>\n\n <ng-template #simpleParentNodeTpl>\n <ng-container [ngTemplateOutlet]=\"parentNodeFulltemplate\" [ngTemplateOutletContext]=\"{$implicit:node}\">\n </ng-container>\n </ng-template>\n </mat-tree-node>\n\n\n </mat-tree>\n </div>\n\n <!-- mat pagination-->\n <mat-paginator *ngIf=\"paginationControls && pageable\" #matpaginator [length]=\"dataSource?.resultsLength\" [pageSize]=\"queryRows\"\n [pageSizeOptions]=\"pageSizeOptions\" [pageIndex]=\"currentPage\" [showFirstLastButtons]=\"true\" (page)=\"onChangePage($event)\"\n [ngStyle]=\"{'visibility': (loading | async) === false ? 'visible':'hidden'}\"></mat-paginator>\n\n\n</div>\n\n<ng-template let-node #leafNodeFulltemplate>\n <ng-container *ngIf=\"leafNodeTemplate; else defaultLeafNodeTpl\" [ngTemplateOutlet]=\"leafNodeTemplate\"\n [ngTemplateOutletContext]=\"{$implicit:node.data}\">\n </ng-container>\n\n <ng-template #defaultLeafNodeTpl>\n {{node.label}}\n </ng-template>\n</ng-template>\n\n<ng-template let-node #parentNodeFulltemplate>\n <ng-container *ngIf=\"parentNodeTemplate; else defaultParentNodeTpl\" [ngTemplateOutlet]=\"parentNodeTemplate\"\n [ngTemplateOutletContext]=\"{$implicit:node.data}\">\n </ng-container>\n\n <ng-template #defaultParentNodeTpl>\n <span>{{node.label}}</span>\n <mat-progress-bar *ngIf=\"node.isLoading\" mode=\"indeterminate\" fxFlex></mat-progress-bar>\n </ng-template>\n</ng-template>\n", styles: [".o-tree .mat-mdc-progress-bar{margin-left:30px}.o-tree .node-wrapper{align-items:center}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i2.OSearchInputComponent, selector: "o-search-input", inputs: ["placeholder", "label", "width", "float-label", "appearance", "columns", "filter-case-sensitive", "show-case-sensitive-checkbox", "show-menu"], outputs: ["onSearch"] }, { kind: "directive", type: i3.DefaultLayoutDirective, selector: " [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]", inputs: ["fxLayout", "fxLayout.xs", "fxLayout.sm", "fxLayout.md", "fxLayout.lg", "fxLayout.xl", "fxLayout.lt-sm", "fxLayout.lt-md", "fxLayout.lt-lg", "fxLayout.lt-xl", "fxLayout.gt-xs", "fxLayout.gt-sm", "fxLayout.gt-md", "fxLayout.gt-lg"] }, { kind: "directive", type: i3.DefaultLayoutAlignDirective, selector: " [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]", inputs: ["fxLayoutAlign", "fxLayoutAlign.xs", "fxLayoutAlign.sm", "fxLayoutAlign.md", "fxLayoutAlign.lg", "fxLayoutAlign.xl", "fxLayoutAlign.lt-sm", "fxLayoutAlign.lt-md", "fxLayoutAlign.lt-lg", "fxLayoutAlign.lt-xl", "fxLayoutAlign.gt-xs", "fxLayoutAlign.gt-sm", "fxLayoutAlign.gt-md", "fxLayoutAlign.gt-lg"] }, { kind: "directive", type: i3.FlexFillDirective, selector: "[fxFill], [fxFlexFill]" }, { kind: "directive", type: i3.DefaultFlexDirective, selector: " [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]", inputs: ["fxFlex", "fxFlex.xs", "fxFlex.sm", "fxFlex.md", "fxFlex.lg", "fxFlex.xl", "fxFlex.lt-sm", "fxFlex.lt-md", "fxFlex.lt-lg", "fxFlex.lt-xl", "fxFlex.gt-xs", "fxFlex.gt-sm", "fxFlex.gt-md", "fxFlex.gt-lg"] }, { kind: "directive", type: i4.DefaultClassDirective, selector: " [ngClass], [ngClass.xs], [ngClass.sm], [ngClass.md], [ngClass.lg], [ngClass.xl], [ngClass.lt-sm], [ngClass.lt-md], [ngClass.lt-lg], [ngClass.lt-xl], [ngClass.gt-xs], [ngClass.gt-sm], [ngClass.gt-md], [ngClass.gt-lg]", inputs: ["ngClass", "ngClass.xs", "ngClass.sm", "ngClass.md", "ngClass.lg", "ngClass.xl", "ngClass.lt-sm", "ngClass.lt-md", "ngClass.lt-lg", "ngClass.lt-xl", "ngClass.gt-xs", "ngClass.gt-sm", "ngClass.gt-md", "ngClass.gt-lg"] }, { kind: "directive", type: i4.DefaultStyleDirective, selector: " [ngStyle], [ngStyle.xs], [ngStyle.sm], [ngStyle.md], [ngStyle.lg], [ngStyle.xl], [ngStyle.lt-sm], [ngStyle.lt-md], [ngStyle.lt-lg], [ngStyle.lt-xl], [ngStyle.gt-xs], [ngStyle.gt-sm], [ngStyle.gt-md], [ngStyle.gt-lg]", inputs: ["ngStyle", "ngStyle.xs", "ngStyle.sm", "ngStyle.md", "ngStyle.lg", "ngStyle.xl", "ngStyle.lt-sm", "ngStyle.lt-md", "ngStyle.lt-lg", "ngStyle.lt-xl", "ngStyle.gt-xs", "ngStyle.gt-sm", "ngStyle.gt-md", "ngStyle.gt-lg"] }, { kind: "component", type: i5.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i6.MatCheckbox, selector: "mat-checkbox", inputs: ["disableRipple", "color", "tabIndex"], exportAs: ["matCheckbox"] }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i8.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", exportAs: ["matMenuTrigger"] }, { kind: "component", type: i9.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i10.MatPaginator, selector: "mat-paginator", inputs: ["disabled"], exportAs: ["matPaginator"] }, { kind: "directive", type: i11.MatTreeNodeDef, selector: "[matTreeNodeDef]", inputs: ["matTreeNodeDefWhen", "matTreeNode"] }, { kind: "directive", type: i11.MatTreeNodePadding, selector: "[matTreeNodePadding]", inputs: ["matTreeNodePadding", "matTreeNodePaddingIndent"] }, { kind: "directive", type: i11.MatTreeNodeToggle, selector: "[matTreeNodeToggle]", inputs: ["matTreeNodeToggleRecursive"] }, { kind: "component", type: i11.MatTree, selector: "mat-tree", exportAs: ["matTree"] }, { kind: "directive", type: i11.MatTreeNode, selector: "mat-tree-node", inputs: ["role", "disabled", "tabIndex"], exportAs: ["matTreeNode"] }, { kind: "component", type: i12.OTreeMenuComponent, selector: "o-tree-menu", inputs: ["select-all-checkbox", "select-all-checkbox-visible"], outputs: ["onSelectCheckboxChange"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i13.OTranslatePipe, name: "oTranslate" }], encapsulation: i0.ViewEncapsulation.None }); __decorate([ BooleanInputConverter(), __metadata("design:type", Boolean) ], OTreeComponent.prototype, "refreshButton", void 0); __decorate([ BooleanInputConverter(), __metadata("design:type", Boolean) ], OTreeComponent.prototype, "deleteButton", void 0); __decorate([ BooleanInputConverter(), __metadata("design:type", Boolean) ], OTreeComponent.prototype, "showButtonsText", void 0); __decorate([ BooleanInputConverter(), __metadata("design:type", Boolean) ], OTreeComponent.prototype, "selectAllCheckboxVisible", void 0); __decorate([ BooleanInputConverter(), __metadata("design:type", Boolean) ], OTreeComponent.prototype, "selectAllCheckbox", void 0); __decorate([ BooleanInputConverter(), __metadata("design:type", Boolean) ], OTreeComponent.prototype, "recursive", void 0); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: OTreeComponent, decorators: [{ type: Component, args: [{ selector: 'o-tree', inputs: DEFAULT_INPUTS_O_TREE, outputs: DEFAULT_OUTPUTS_O_TREE, encapsulation: ViewEncapsulation.None, host: { '[class.o-tree]': 'true' }, providers: [ OTreeDao, OntimizeServiceProvider, ComponentStateServiceProvider, { provide: O_COMPONENT_STATE_SERVICE, useClass: OTreeComponentStateService }, ], template: "<div fxLayout=\"column\" fxFlexFill>\n <div fxFlex>\n <div *ngIf=\"hasControls()\" fxLayout=\"row\" fxLayoutAlign=\"start center\">\n\n <div class=\"buttons\">\n <ng-container *ngIf=\"showButtonsText;else showButtonsWithoutText\">\n\n <button *ngIf=\"insertButton\" type=\"button\" mat-stroked-button aria-label=\"Insert\" [disabled]=\"!enabledInsertButton\" (click)=\"add($event)\">\n <mat-icon svgIcon=\"ontimize:add\"></mat-icon>\n <span>{{ 'INSERT' | oTranslate }}</span>\n </button>\n\n <button *ngIf=\"refreshButton\" type=\"button\" mat-stroked-button aria-label=\"Refresh\" [disabled]=\"!enabledRefreshButton\"\n (click)=\"reloadData()\">\n <mat-icon svgIcon=\"ontimize:autorenew\"></mat-icon>\n <span>{{ 'REFRESH' | oTranslate }}</span>\n </button>\n\n <button *ngIf=\"deleteButton\" type=\"button\" mat-stroked-button aria-label=\"Delete\" [disabled]=\"!enabledDeleteButton\"\n [class.disabled]=\"!enabledDeleteButton\">\n <mat-icon svgIcon=\"ontimize:delete\"></mat-icon>\n <span>{{ 'DELETE' | oTranslate }}</span>\n </button>\n\n </ng-container>\n\n <ng-template #showButtonsWithoutText>\n <button *ngIf=\"insertButton\" type=\"button\" mat-icon-button aria-label=\"Insert\" [disabled]=\"!enabledInsertButton\" (click)=\"add($event)\">\n <mat-icon svgIcon=\"ontimize:add\"></mat-icon>\n </button>\n\n <button *ngIf=\"refreshButton\" type=\"button\" mat-icon-button aria-label=\"Refresh\" [disabled]=\"!enabledRefreshButton\" (click)=\"reloadData()\">\n <mat-icon svgIcon=\"ontimize:autorenew\"></mat-icon>\n </button>\n\n <button *ngIf=\"deleteButton\" type=\"button\" mat-icon-button aria-label=\"Delete\" [disabled]=\"!enabledDeleteButton\"\n [class.disabled]=\"!enabledDeleteButton\">\n <mat-icon svgIcon=\"ontimize:delete\"></mat-icon>\n </button>\n </ng-template>\n\n <ng-content select=\"[o-tree-button]\"></ng-content>\n </div>\n <div fxFlex>\n <span fxLayoutAlign=\"center center\" class=\"title\">{{ title | oTranslate }}</span>\n </div>\n <o-search-input *ngIf=\"quickFilter\" [filter-case-sensitive]=\"filterCaseSensitive\" [show-case-sensitive-checkbox]=\"showCaseSensitiveCheckbox()\"\n [columns]=\"quickFilterColumns\" [placeholder]=\"quickFilterPlaceholder\" appearance=\"outline\" float-label=\"never\" show-menu=\"no\">\n </o-search-input>\n <button type=\"button\" *ngIf=\"showTreeMenuButton\" mat-icon-button class=\"o-tree-menu-button\" [matMenuTriggerFor]=\"treeMenu.matMenu\"\n (click)=\"$event.stopPropagation()\">\n <mat-icon svgIcon=\"ontimize:more_vert\"></mat-icon>\n </button>\n <o-tree-menu #treeMenu [select-all-checkbox]=\"selectAllCheckbox\" [select-all-checkbox-visible]=\"selectAllCheckboxVisible\"\n (onSelectCheckboxChange)=\"onSelectCheckboxChange($event)\"></o-tree-menu>\n\n </div>\n\n <mat-tree [dataSource]=\"dataSource\" [treeControl]=\"treeControl\">\n\n <!-- This is the tree node template for leaf nodes -->\n <mat-tree-node *matTreeNodeDef=\"let node\" (click)=\"leafNodeClicked($event, node)\" matTreeNodeToggle matTreeNodePadding\n [ngClass]=\"{'selected-node': isSelectedNode(node)}\">\n <!-- use a disabled button to provide padding for tree leaf -->\n <div fxLayout=\"column\">\n <div fxLayout=\"row\">\n <button mat-icon-button disabled></button>\n\n <ng-container *ngIf=\"selectAllCheckboxVisible; else simpleLeafNodeTpl\">\n <div class=\"node-wrapper\">\n <mat-checkbox *ngIf=\"selectAllCheckboxVisible; else simpleLeafNodeTpl\" [checked]=\"selection.isSelected(node)\"\n (click)=\"checkboxClicked($event)\" (change)=\"todoLeafItemSelectionToggle(node)\" (keyDown)=\"checkboxClicked($event)\">\n </mat-checkbox>\n <ng-container [ngTemplateOutlet]=\"leafNodeFulltemplate\" [ngTemplateOutletContext]=\"{$implicit:node}\">\n </ng-container>\n </div>\n </ng-container>\n <ng-template #simpleLeafNodeTpl>\n <ng-container [ngTemplateOutlet]=\"leafNodeFulltemplate\" [ngTemplateOutletContext]=\"{$implicit:node}\">\n </ng-container>\n </ng-template>\n </div>\n <div *ngIf=\"isLastChildAndHasMore(node)\">\n <button #buttonLoadMore mat-button color=\"primary\" (click)=\"onLoadMore($event, node)\">\n {{ 'O_TREE.LOAD_MORE' | oTranslate}}\n </button>\n </div>\n </div>\n\n </mat-tree-node>\n <!-- This is the tree node template for expandable nodes -->\n <mat-tree-node *matTreeNodeDef=\"let node; when: hasChild\" (click)=\"parentNodeClicked($event, node)\" matTreeNodePadding\n [ngClass]=\"{'selected-node': isSelectedNode(node)}\">\n <button mat-icon-button [attr.aria-label]=\"'toggle ' + node.label\" matTreeNodeToggle (click)=\"onClickToggleButton($event, node)\">\n <mat-icon class=\"mat-icon-rtl-mirror\">\n {{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}\n </mat-icon>\n </button>\n\n <ng-container *ngIf=\"selectAllCheckboxVisible; else simpleParentNodeTpl\">\n <div class=\"node-wrapper\">\n <mat-checkbox *ngIf=\"selectAllCheckboxVisible; else simpleParentNodeTpl\" (click)=\"checkboxClicked($event)\"\n (change)=\"todoItemSelectionToggle(node)\" [checked]=\"descendantsAllSelected(node)\" [indeterminate]=\"descendantsPartiallySelected(node)\">\n </mat-checkbox>\n <ng-container [ngTemplateOutlet]=\"parentNodeFulltemplate\" [ngTemplateOutletContext]=\"{$implicit:node}\">\n </ng-container>\n </div>\n </ng-container>\n\n <ng-template #simpleParentNodeTpl>\n <ng-container [ngTemplateOutlet]=\"parentNodeFulltemplate\" [ngTemplateOutletContext]=\"{$implicit:node}\">\n </ng-container>\n </ng-template>\n </mat-tree-node>\n\n\n </mat-tree>\n </div>\n\n <!-- mat pagination-->\n <mat-paginator *ngIf=\"paginationControls && pageable\" #matpaginator [length]=\"dataSource?.resultsLength\"