From bd494a7108d0dabdf5a149adcd6036ad596880a7 Mon Sep 17 00:00:00 2001 From: Iakovleva Margarita Date: Wed, 25 Mar 2020 18:49:02 +0300 Subject: [PATCH] feat(docs): adding checklist tree example (#UIM-391) (#429) 8x --- .../mosaic-examples/mosaic/tree/module.ts | 8 +- .../tree-multiple-checklist-example.css | 3 + .../tree-multiple-checklist-example.html | 21 ++ .../tree-multiple-checklist-example.ts | 241 ++++++++++++++++++ packages/mosaic/tree/tree.md | 3 + 5 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 packages/mosaic-examples/mosaic/tree/tree-multiple-checklist/tree-multiple-checklist-example.css create mode 100644 packages/mosaic-examples/mosaic/tree/tree-multiple-checklist/tree-multiple-checklist-example.html create mode 100644 packages/mosaic-examples/mosaic/tree/tree-multiple-checklist/tree-multiple-checklist-example.ts diff --git a/packages/mosaic-examples/mosaic/tree/module.ts b/packages/mosaic-examples/mosaic/tree/module.ts index d35d84e3f..faef51f2f 100644 --- a/packages/mosaic-examples/mosaic/tree/module.ts +++ b/packages/mosaic-examples/mosaic/tree/module.ts @@ -1,5 +1,6 @@ import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; +import { McCheckboxModule } from '@ptsecurity/mosaic/checkbox'; import { McHighlightModule } from '@ptsecurity/mosaic/core'; import { McFormFieldModule } from '@ptsecurity/mosaic/form-field'; import { McIconModule } from '@ptsecurity/mosaic/icon'; @@ -8,6 +9,9 @@ import { McTreeModule } from '@ptsecurity/mosaic/tree'; import { TreeFilteringExample } from './tree-filtering/tree-filtering-example'; import { TreeMultipleCheckboxExample } from './tree-multiple-checkbox/tree-multiple-checkbox-example'; +import { + TreeMultipleChecklistExample +} from './tree-multiple-checklist/tree-multiple-checklist-example'; import { TreeMultipleKeyboardExample } from './tree-multiple-keyboard/tree-multiple-keyboard-example'; import { TreeOverviewExample } from './tree-overview/tree-overview-example'; @@ -15,6 +19,7 @@ import { TreeOverviewExample } from './tree-overview/tree-overview-example'; const EXAMPLES = [ TreeOverviewExample, TreeMultipleCheckboxExample, + TreeMultipleChecklistExample, TreeMultipleKeyboardExample, TreeFilteringExample ]; @@ -26,7 +31,8 @@ const EXAMPLES = [ McInputModule, McTreeModule, McIconModule, - McHighlightModule + McHighlightModule, + McCheckboxModule ], declarations: EXAMPLES, exports: EXAMPLES diff --git a/packages/mosaic-examples/mosaic/tree/tree-multiple-checklist/tree-multiple-checklist-example.css b/packages/mosaic-examples/mosaic/tree/tree-multiple-checklist/tree-multiple-checklist-example.css new file mode 100644 index 000000000..9b8390484 --- /dev/null +++ b/packages/mosaic-examples/mosaic/tree/tree-multiple-checklist/tree-multiple-checklist-example.css @@ -0,0 +1,3 @@ +.tree-example-checkbox { + margin-right: 8px; +} diff --git a/packages/mosaic-examples/mosaic/tree/tree-multiple-checklist/tree-multiple-checklist-example.html b/packages/mosaic-examples/mosaic/tree/tree-multiple-checklist/tree-multiple-checklist-example.html new file mode 100644 index 000000000..92730ec59 --- /dev/null +++ b/packages/mosaic-examples/mosaic/tree/tree-multiple-checklist/tree-multiple-checklist-example.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + + diff --git a/packages/mosaic-examples/mosaic/tree/tree-multiple-checklist/tree-multiple-checklist-example.ts b/packages/mosaic-examples/mosaic/tree/tree-multiple-checklist/tree-multiple-checklist-example.ts new file mode 100644 index 000000000..cf3dfb91e --- /dev/null +++ b/packages/mosaic-examples/mosaic/tree/tree-multiple-checklist/tree-multiple-checklist-example.ts @@ -0,0 +1,241 @@ +/* tslint:disable:no-reserved-keywords object-literal-key-quotes */ +import { SelectionModel } from '@angular/cdk/collections'; +import { Component } from '@angular/core'; +import { FlatTreeControl } from '@ptsecurity/cdk/tree'; +import { McTreeFlatDataSource, McTreeFlattener } from '@ptsecurity/mosaic/tree'; + + +export class FileNode { + children: FileNode[]; + name: string; + type: any; +} + +/** Flat node with expandable and level information */ +export class FileFlatNode { + name: string; + type: any; + level: number; + expandable: boolean; + parent: any; +} + +/** + * Build the file structure tree. The `value` is the Json object, or a sub-tree of a Json object. + * The return value is the list of `FileNode`. + */ +export function buildFileTree(value: any, level: number): FileNode[] { + const data: any[] = []; + + for (const k of Object.keys(value)) { + const v = value[k]; + const node = new FileNode(); + + node.name = `${k}`; + if (v === null || v === undefined) { + // no action + } else if (typeof v === 'object') { + node.children = buildFileTree(v, level + 1); + } else { + node.type = v; + } + + data.push(node); + } + + return data; +} + +export const DATA_OBJECT = { + docs: 'app', + src: { + cdk: { + a11ly: { + 'aria-describer': { + 'aria-describer': 'ts', + 'aria-describer.spec': 'ts', + 'aria-reference': 'ts', + 'aria-reference.spec': 'ts' + }, + 'focus-monitor': { + 'focus-monitor': 'ts', + 'focus-monitor.spec': 'ts' + } + } + }, + documentation: { + source: '', + tools: '' + }, + mosaic: { + autocomplete: '', + button: '', + 'button-toggle': '', + index: 'ts', + package: 'json', + version: 'ts' + }, + 'mosaic-dev': { + alert: '', + badge: '' + }, + 'mosaic-examples': '', + 'mosaic-moment-adapter': '', + README: 'md', + 'tsconfig.build': 'json', + wallabyTest: 'ts' + }, + scripts: { + deploy: { + 'cleanup-preview': 'ts', + 'publish-artifacts': 'sh', + 'publish-docs': 'sh', + 'publish-docs-preview': 'ts' + }, + 'tsconfig.deploy': 'json' + }, + tests: '' +}; + +/** + * @title Checklist tree + */ +@Component({ + selector: 'tree-multiple-checklist-example', + templateUrl: 'tree-multiple-checklist-example.html', + styleUrls: ['tree-multiple-checklist-example.css'] +}) +export class TreeMultipleChecklistExample { + treeControl: FlatTreeControl; + treeFlattener: McTreeFlattener; + + dataSource: McTreeFlatDataSource; + + modelValue: any = []; + + /** The selection for checklist */ + checklistSelection = new SelectionModel(true /* multiple */); + + constructor() { + this.treeFlattener = new McTreeFlattener( + this.transformer, this.getLevel, this.isExpandable, this.getChildren + ); + + this.treeControl = new FlatTreeControl( + this.getLevel, this.isExpandable, this.getValue, this.getViewValue + ); + this.dataSource = new McTreeFlatDataSource(this.treeControl, this.treeFlattener); + + this.dataSource.data = buildFileTree(DATA_OBJECT, 0); + } + + hasChild(_: number, nodeData: FileFlatNode) { return nodeData.expandable; } + + /** Whether all the descendants of the node are selected. */ + descendantsAllSelected(node: FileFlatNode): boolean { + const descendants = this.treeControl.getDescendants(node); + + return descendants.every((child) => this.checklistSelection.isSelected(child)); + } + + /** Whether part of the descendants are selected */ + descendantsPartiallySelected(node: FileFlatNode): boolean { + const descendants = this.treeControl.getDescendants(node); + const result = descendants.some((child) => this.checklistSelection.isSelected(child)); + + return result && !this.descendantsAllSelected(node); + } + + /** Toggle the to-do item selection. Select/deselect all the descendants node */ + fileSelectionToggle(node: FileFlatNode): void { + this.checklistSelection.toggle(node); + const descendants = this.treeControl.getDescendants(node); + this.checklistSelection.isSelected(node) + ? this.checklistSelection.select(...descendants) + : this.checklistSelection.deselect(...descendants); + + // Force update for the parent + descendants.every((child) => + this.checklistSelection.isSelected(child) + ); + this.checkAllParentsSelection(node); + } + + /* Checks all the parents when a leaf node is selected/unselected */ + checkAllParentsSelection(node: FileFlatNode): void { + let parent: FileFlatNode | null = this.getParentNode(node); + while (parent) { + this.checkRootNodeSelection(parent); + parent = this.getParentNode(parent); + } + } + + /** Check root node checked state and change it accordingly */ + checkRootNodeSelection(node: FileFlatNode): void { + const nodeSelected = this.checklistSelection.isSelected(node); + const descendants = this.treeControl.getDescendants(node); + const descAllSelected = descendants.every((child) => + this.checklistSelection.isSelected(child) + ); + if (nodeSelected && !descAllSelected) { + this.checklistSelection.deselect(node); + } else if (!nodeSelected && descAllSelected) { + this.checklistSelection.select(node); + } + } + + /* Get the parent node of a node */ + getParentNode(node: FileFlatNode): FileFlatNode | null { + 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) { + return currentNode; + } + } + + return null; + } + + private transformer = (node: FileNode, level: number, parent: any) => { + const flatNode = new FileFlatNode(); + + flatNode.name = node.name; + flatNode.parent = parent; + flatNode.type = node.type; + flatNode.level = level; + flatNode.expandable = !!node.children; + + return flatNode; + } + + private getLevel = (node: FileFlatNode) => { + return node.level; + } + + private isExpandable = (node: FileFlatNode) => { + return node.expandable; + } + + private getChildren = (node: FileNode): FileNode[] => { + return node.children; + } + + private getValue = (node: FileNode): string => { + return node.name; + } + + private getViewValue = (node: FileNode): string => { + const nodeType = node.type ? `.${node.type}` : ''; + + return `${node.name}${nodeType}`; + } +} diff --git a/packages/mosaic/tree/tree.md b/packages/mosaic/tree/tree.md index ac65da058..ba67bf2ff 100644 --- a/packages/mosaic/tree/tree.md +++ b/packages/mosaic/tree/tree.md @@ -4,6 +4,9 @@ ### Multiple mode with checkboxes +### Multiple mode with checkboxes with child selection + + ### Multiple mode without checkboxes