1 | import { TreeNode } from '../types/Node'
|
2 | import { TreeComponent } from '../types/Tree'
|
3 | import { State } from '../types/State'
|
4 | import { Core, PromiseCallback, PromiseNodes, Resource, InsertOptions } from '../types/Core'
|
5 | import { callFetcher, isCallable, isString, remove, has } from '../utils'
|
6 | import { parseNode } from '../utils/parser'
|
7 | import { recurseDown, walkBreadth, FlatMap, flatMap } from '../utils/traveler'
|
8 | import { find } from '../utils/find'
|
9 |
|
10 | function parseOpts(opts?: InsertOptions): InsertOptions {
|
11 | try {
|
12 | return Object.assign({}, {loading: true}, opts)
|
13 | } catch(e) {
|
14 | return {
|
15 | loading: true
|
16 | }
|
17 | }
|
18 | }
|
19 |
|
20 | export default class CoreTree implements Core {
|
21 | private state: State
|
22 | private tree: TreeComponent
|
23 |
|
24 | constructor(tree: TreeComponent, state: State) {
|
25 | this.state = state
|
26 | this.tree = tree
|
27 | }
|
28 |
|
29 | flatMap = (collection: TreeNode[], ignoreCollapsed?: boolean): FlatMap => {
|
30 | return flatMap(collection, ignoreCollapsed)
|
31 | }
|
32 |
|
33 | find<T>(target: TreeNode[], multiple: boolean, criterias: any): T | null {
|
34 | return find(target, walkBreadth, multiple, criterias)
|
35 | }
|
36 |
|
37 | set = (node: TreeNode, key: string, value: any): void => {
|
38 |
|
39 | this.state.set(node.id, key, value)
|
40 | this.tree.updateState()
|
41 | }
|
42 |
|
43 | clearKeys = (node: TreeNode, includeSelf: boolean = false): void => {
|
44 | const selected: string[] = this.tree.selected
|
45 | const checked: string[] = this.tree.checked
|
46 | const indeterminate: string[] = this.tree.indeterminate
|
47 |
|
48 | recurseDown(node, (child: TreeNode) => {
|
49 | if (child.selected) {
|
50 | remove(selected, child.id)
|
51 | }
|
52 |
|
53 | if (child.checked) {
|
54 | remove(checked, child.id)
|
55 | }
|
56 |
|
57 | remove(indeterminate, child.id)
|
58 | }, includeSelf)
|
59 | }
|
60 |
|
61 | load = (node: TreeNode, resource: Resource, showLoading?: boolean): PromiseNodes => {
|
62 | const result = callFetcher(node, resource)
|
63 |
|
64 | if (showLoading) {
|
65 | this.set(node, 'loading', true)
|
66 | }
|
67 |
|
68 | return result.then((items: any) => {
|
69 | if (showLoading) {
|
70 | this.state.set(node.id, 'loading', false)
|
71 | }
|
72 |
|
73 | return parseNode(items)
|
74 | })
|
75 | }
|
76 |
|
77 | insertAt = (node: TreeNode, resource: Resource, insertIndex: number): PromiseNodes | TreeNode[] => {
|
78 | const parent: TreeNode | null = node.parent || null
|
79 | const insert = (nodes: TreeNode[]) => {
|
80 | this.state.insertAt(
|
81 | parent ? parent : null,
|
82 | nodes,
|
83 | insertIndex
|
84 | )
|
85 | this.tree.updateState()
|
86 | return nodes
|
87 | }
|
88 |
|
89 | if (isCallable(resource)) {
|
90 | return this.load(node, resource as PromiseCallback, false).then(insert)
|
91 | } else {
|
92 | return insert(parseNode(resource))
|
93 | }
|
94 | }
|
95 |
|
96 | addChild = (node: TreeNode, resource: Resource, insertIndex: number | undefined, opts?: InsertOptions): PromiseNodes | TreeNode => {
|
97 | const id: string = node.id
|
98 | const {expand, loading} = parseOpts(opts)
|
99 |
|
100 | if (isCallable(resource)) {
|
101 | return this.load(node, resource, loading).then((nodes: TreeNode[]) => {
|
102 | this.tree.addChild(id, nodes, insertIndex)
|
103 |
|
104 | const updatedNode: TreeNode | null = this.state.byId(id)
|
105 |
|
106 | if (expand && updatedNode && !updatedNode.expanded) {
|
107 | this.tree.expand(updatedNode)
|
108 | }
|
109 |
|
110 | this.tree.updateState()
|
111 |
|
112 | return node
|
113 | })
|
114 | } else {
|
115 | this.tree.addChild(id, resource)
|
116 |
|
117 | if (expand && !node.expanded) {
|
118 | this.tree.expand(node)
|
119 | }
|
120 |
|
121 | this.tree.updateState()
|
122 | }
|
123 |
|
124 | return node
|
125 | }
|
126 |
|
127 | remove = (node: TreeNode): TreeNode | null => {
|
128 | const removedNode: TreeNode | null = this.state.remove(node.id)
|
129 |
|
130 | if (removedNode) {
|
131 | removedNode.parent = null
|
132 |
|
133 | this.clearKeys(removedNode)
|
134 | this.tree.updateState()
|
135 | this.tree.$emit('onRemove', removedNode)
|
136 | }
|
137 |
|
138 | return removedNode
|
139 | }
|
140 |
|
141 | data = (node: TreeNode, key: any, value?: any): any => {
|
142 | if (!key && !value) {
|
143 | return node.data
|
144 | }
|
145 |
|
146 | if (undefined === value && isString(key)) {
|
147 | return node.data[key]
|
148 | }
|
149 |
|
150 | let data
|
151 |
|
152 | if (!isString(key)) {
|
153 | data = key
|
154 | } else {
|
155 | node.data[key] = value
|
156 | data = node.data
|
157 | }
|
158 |
|
159 | this.state.set(node.id, 'data', data)
|
160 | this.tree.updateState()
|
161 |
|
162 | return node
|
163 | }
|
164 |
|
165 | hasClass = (node: TreeNode, className: string): boolean => {
|
166 | return !!node.className && new RegExp(className).test(node.className)
|
167 | }
|
168 |
|
169 | removeClass(node: TreeNode, classNames: string[]): TreeNode {
|
170 | const className: string = (node.className || "")
|
171 | .split(' ')
|
172 | .filter((klazz: string) => !has(classNames, klazz))
|
173 | .join(' ')
|
174 |
|
175 | this.set(node, 'className', className)
|
176 |
|
177 | return node
|
178 | }
|
179 |
|
180 | addClass(node: TreeNode, classNames: string[]): TreeNode {
|
181 | const className: string[] = node.className ? node.className.split(' ') : []
|
182 |
|
183 | classNames.forEach((klazz: string) => {
|
184 | if (!has(className, "" + klazz)) {
|
185 | className.push(klazz)
|
186 | }
|
187 | })
|
188 |
|
189 | this.set(node, 'className', className.join(' '))
|
190 |
|
191 | return node
|
192 | }
|
193 |
|
194 | } |
\ | No newline at end of file |