UNPKG

15.5 kBJavaScriptView Raw
1import React from 'react';
2import ReactDOM from 'react-dom'
3import classNames from 'classnames';
4import Animate from 'bee-animate';
5import {
6 browser,
7 getNodeChildren,
8 toArray,
9 warnOnlyTreeNode
10} from './util';
11import PropTypes from 'prop-types';
12import { KeyCode } from 'tinper-bee-core';
13const browserUa = typeof window !== 'undefined' ? browser(window.navigator) : '';
14const ieOrEdge = /.*(IE|Edge).+/.test(browserUa);
15// const uaArray = browserUa.split(' ');
16// const gtIE8 = uaArray.length !== 2 || uaArray[0].indexOf('IE') === -1 || Number(uaArray[1]) > 8;
17
18const defaultTitle = '---';
19
20class TreeNode extends React.Component {
21 constructor(props) {
22 super(props);
23 [
24 'onExpand',
25 'onCheck',
26 'onContextMenu',
27 'onMouseEnter',
28 'onMouseLeave',
29 'onDragStart',
30 'onDragEnter',
31 'onDragOver',
32 'onDragLeave',
33 'onDrop',
34 'onDragEnd',
35 'onDoubleClick',
36 'onKeyDown'
37 ].forEach((m) => {
38 this[m] = this[m].bind(this);
39 });
40 this.state = {
41 dataLoading: false,
42 dragNodeHighlight: false,
43 };
44 }
45
46 componentDidMount() {
47 if (!this.props.root._treeNodeInstances) {
48 this.props.root._treeNodeInstances = [];
49 }
50 this.props.root._treeNodeInstances.push(this);
51 }
52 // shouldComponentUpdate(nextProps) {
53 // if (!nextProps.expanded) {
54 // return false;
55 // }
56 // return true;
57 // }
58
59 onCheck() {
60
61 this.props.root.onCheck(this);
62 }
63
64 onSelect() {
65 clearTimeout(this.doubleClickFlag);
66 let _this = this;
67 if(this.props.onDoubleClick){
68 //执行延时
69 this.doubleClickFlag = setTimeout(function(){
70 //do function在此处写单击事件要执行的代码
71 _this.props.root.onSelect(_this);
72 },300);
73 }else{
74 _this.props.root.onSelect(_this);
75 }
76
77
78 }
79
80
81 onDoubleClick() {
82 clearTimeout(this.doubleClickFlag);
83 this.props.root.onDoubleClick(this);
84 }
85
86 onMouseEnter(e) {
87 e.preventDefault();
88 this.props.root.onMouseEnter(e, this);
89 }
90
91 onMouseLeave(e) {
92 e.preventDefault();
93 this.props.root.onMouseLeave(e, this);
94 }
95
96 onContextMenu(e) {
97 e.preventDefault();
98 this.props.root.onContextMenu(e, this);
99 }
100
101 onDragStart(e) {
102 // console.log('dragstart', this.props.eventKey, e);
103 // e.preventDefault();
104 e.stopPropagation();
105 this.setState({
106 dragNodeHighlight: true,
107 });
108 this.props.root.onDragStart(e, this);
109 try {
110 // ie throw error
111 // firefox-need-it
112 e.dataTransfer.setData('text/plain', '');
113 } finally {
114 // empty
115 }
116 }
117
118 onDragEnter(e) {
119 e.preventDefault();
120 e.stopPropagation();
121 this.props.root.onDragEnter(e, this);
122 }
123
124 onDragOver(e) {
125 // todo disabled
126 e.preventDefault();
127 e.stopPropagation();
128 this.props.root.onDragOver(e, this);
129 return false;
130 }
131
132 onDragLeave(e) {
133 e.stopPropagation();
134 this.props.root.onDragLeave(e, this);
135 }
136
137 onDrop(e) {
138 e.preventDefault();
139 e.stopPropagation();
140 this.setState({
141 dragNodeHighlight: false,
142 });
143 this.props.root.onDrop(e, this);
144 }
145
146 onDragEnd(e) {
147 e.stopPropagation();
148 this.setState({
149 dragNodeHighlight: false,
150 });
151 this.props.root.onDragEnd(e, this);
152 }
153
154 onExpand() {
155 const callbackPromise = this.props.root.onExpand(this);
156 if (callbackPromise && typeof callbackPromise === 'object') {
157 const setLoading = (dataLoading) => {
158 this.setState({
159 dataLoading
160 });
161 };
162 setLoading(true);
163 callbackPromise.then(() => {
164 setLoading(false);
165 }, () => {
166 setLoading(false);
167 });
168 }
169 }
170
171 // keyboard event support
172 onKeyDown(e) {
173 this.props.root.onKeyDown(e,this);
174 if(e.keyCode == KeyCode.SPACE || e.keyCode == KeyCode.DOWN || e.keyCode == KeyCode.LEFT || e.keyCode == KeyCode.RIGHT ||e.keyCode == KeyCode.UP){
175 e.preventDefault();
176 }
177
178 }
179
180 renderSwitcher(props, expandedState) {
181 let stateIcon;
182 const prefixCls = props.prefixCls;
183 const switcherCls = {
184 [`${prefixCls}-switcher`]: true
185 };
186 if (!props.showLine) {
187 switcherCls[`${prefixCls}-noline_${expandedState}`] = true;
188 } else if (props.pos === '0-0') {
189 switcherCls[`${prefixCls}-roots_${expandedState}`] = true;
190 } else {
191 switcherCls[`${prefixCls}-center_${expandedState}`] = !props.last;
192 switcherCls[`${prefixCls}-bottom_${expandedState}`] = props.last;
193 }
194
195 if (expandedState === 'open' && props.openIcon) {
196 stateIcon = props.openIcon;
197 switcherCls['icon-none'] = true;
198 }
199 if (expandedState === 'close' && props.closeIcon) {
200 stateIcon = props.closeIcon;
201 switcherCls['icon-none'] = true;
202 }
203 //switcherCls[stateIcon] = stateIcon;
204 props.switcherClass?switcherCls[`${props.switcherClass}`]=true:'';
205 if (props.disabled && !props.mustExpandable) {
206 switcherCls[`${prefixCls}-switcher-disabled`] = true;
207 return <span className={classNames(switcherCls)} style={props.switcherStyle}>{stateIcon}</span>;
208 }
209 return <span className={classNames(switcherCls)} style={props.switcherStyle} onClick={this.onExpand}>{stateIcon}</span>;
210 }
211
212 renderCheckbox(props) {
213 const prefixCls = props.prefixCls;
214 const checkboxCls = {
215 [`${prefixCls}-checkbox`]: true,
216 };
217 if (props.checked) {
218 checkboxCls[`${prefixCls}-checkbox-checked`] = true;
219 } else if (props.halfChecked) {
220 checkboxCls[`${prefixCls}-checkbox-indeterminate`] = true;
221 }
222 let customEle = null;
223 if (typeof props.checkable !== 'boolean') {
224 customEle = props.checkable;
225 }
226 if (props.disabled || props.disableCheckbox) {
227 checkboxCls[`${prefixCls}-checkbox-disabled`] = true;
228 return <span className={classNames(checkboxCls)}>{customEle}</span>;
229 }
230 return (
231 <span
232 className={classNames(checkboxCls) }
233 onClick={this.onCheck}
234 >{customEle}</span>);
235 }
236
237 renderChildren(props) {
238 const renderFirst = this.renderFirst;
239 this.renderFirst = 1;
240 let transitionAppear = true;
241 if (!renderFirst && props.expanded) {
242 transitionAppear = false;
243 }
244 const children = props.children;
245 let newChildren = children;
246 // 确定所有子节点是否是TreeNode
247 let allTreeNode = false;
248 if (Array.isArray(children)) {
249 for( let index = 0; index < children.length; index++ ){
250 let item = children[index];
251 allTreeNode = item.type.isTreeNode == 1;
252 if( !allTreeNode ){
253 //当检查到子节点中有不是 TreeNode 的,则直接结束检查。同时不会渲染所有子节点
254 break;
255 }
256 }
257
258 }else if(children && children.type && children.type.isTreeNode == 1){
259 allTreeNode = true;
260 }
261 // 如果props.children的长度大于0才可以生成子对象
262 if (allTreeNode && React.Children.count(children) ){
263 const cls = {
264 [`${props.prefixCls}-child-tree`]: true,
265 [`${props.prefixCls}-child-tree-open`]: props.expanded,
266 };
267 if (props.showLine) {
268 cls[`${props.prefixCls}-line`] = !props.last;
269 }
270 const animProps = {};
271 if (props.openTransitionName) {
272 animProps.transitionName = props.openTransitionName;
273 } else if (typeof props.openAnimation === 'object') {
274 animProps.animation = Object.assign({}, props.openAnimation);
275 if (!transitionAppear) {
276 delete animProps.animation.appear;
277 }
278 }
279 newChildren = (
280 <Animate {...animProps}
281 showProp="data-expanded"
282 transitionAppear={transitionAppear}
283 component=""
284 >
285 {!props.expanded ? null : <ul className={classNames(cls)} data-expanded={props.expanded}>
286 {React.Children.map(children, (item, index) => {
287 return props.root.renderTreeNode(item, index, props.pos);
288 }, props.root)}
289 </ul>}
290 </Animate>
291 );
292 }
293 return newChildren;
294 }
295
296 getNodeChildren = () => {
297 const { children } = this.props;
298 const originList = toArray(children).filter(node => node);
299 const targetList = getNodeChildren(originList);
300
301 if (originList.length !== targetList.length) {
302 warnOnlyTreeNode();
303 }
304
305 return targetList;
306 };
307 /**
308 *判断是否为叶子节点,isLeaf的优先级>props.children。如果是异步加载是根据isLeaf的值进行判断的
309 *
310 * @returns
311 * @memberof TreeNode
312 */
313 checkIsLeaf(){
314 const { isLeaf, loadData } = this.props;
315 let rs = isLeaf
316 if (rs === false || rs === true) {
317 return rs;
318 }else{
319 const hasChildren = this.getNodeChildren().length !== 0;
320 return (!loadData && !hasChildren);
321 }
322
323
324 }
325
326 render() {
327 const props = this.props;
328 const prefixCls = props.prefixCls;
329 const expandedState = props.expanded ? 'open' : 'close';
330 let iconState = expandedState;
331
332 let canRenderSwitcher = true;
333 const content = props.title;
334 let newChildren = this.renderChildren(props);
335 let openIconCls = false,
336 closeIconCls = false;
337
338 //以下变量控制是否鼠标单机双击方法中的变量
339 let timer = 0;
340 let delay = 500;
341 let prevent = false;
342
343 // if (!newChildren || newChildren === props.children) {
344 // // content = newChildren;
345 // newChildren = null;
346 // if (!props.loadData || props.isLeaf) {
347 // canRenderSwitcher = false;
348 // iconState = 'docu';
349 // }
350 // }
351 if(this.checkIsLeaf()){
352 canRenderSwitcher = false;
353 iconState = 'docu';
354 }
355 // For performance, does't render children into dom when `!props.expanded` (move to Animate)
356 // if (!props.expanded) {
357 // newChildren = null;
358 // }
359
360 const iconEleCls = {
361 [`${prefixCls}-iconEle`]: true,
362 [`${prefixCls}-icon_loading`]: this.state.dataLoading,
363 [`${prefixCls}-icon__${iconState}`]: true
364 };
365 const selectHandle = () => {
366 const titleClass=props.titleClass?prefixCls+'-title'+' '+props.className:prefixCls+'-title';
367 // const icon = (props.showIcon || props.loadData && this.state.dataLoading) ?
368 // <span className={classNames(iconEleCls)}></span> : null;
369 let icon;
370 if(props.showIcon && props.icon){
371 icon = <span
372 className={classNames(
373 `${prefixCls}-iconEle`,
374 `${prefixCls}-icon__customize`,
375 )}
376 >
377 {typeof currentIcon === 'function' ?
378 React.createElement(props.icon, {
379 ...this.props,
380 }) : props.icon}
381 </span>
382 }else if(props.showIcon || props.loadData && this.state.dataLoading){
383 icon = <span className={classNames(iconEleCls)}></span>
384 }
385 const title = <span className={titleClass} style={props.titleStyle} >{content}</span>;
386 const wrap = `${prefixCls}-node-content-wrapper`;
387 const domProps = {
388 className: `${wrap} ${wrap}-${iconState === expandedState ? iconState : 'normal'}`,
389 };
390 if (!props.disabled) {
391 if (props.selected || !props._dropTrigger && this.state.dragNodeHighlight) {
392 domProps.className += ` ${prefixCls}-node-selected`;
393 }
394 domProps.onClick = (e) => {
395 var _this = this;
396 e.preventDefault();
397 if (props.selectable) {
398 _this.onSelect();
399 }
400
401 // not fire check event
402 // if (props.checkable) {
403 // this.onCheck();
404 // }
405 };
406
407 if(props.onDoubleClick){
408 domProps.onDoubleClick = this.onDoubleClick;
409 }
410
411 if (props.onRightClick) {
412 domProps.onContextMenu = this.onContextMenu;
413 }
414 if (props.onMouseEnter) {
415 domProps.onMouseEnter = this.onMouseEnter;
416 }
417 if (props.onMouseLeave) {
418 domProps.onMouseLeave = this.onMouseLeave;
419 }
420
421
422 if (props.draggable) {
423 domProps.className += ' draggable';
424 if (ieOrEdge) {
425 // ie bug!
426 domProps.href = '#';
427 }
428 domProps.draggable = true;
429 domProps['aria-grabbed'] = true;
430 domProps.onDragStart = this.onDragStart;
431 }
432 }
433 //设置tabIndex
434 if(props.focusable){
435 domProps.onKeyDown = this.onKeyDown;
436 domProps.tabIndex = -1;
437 if(props.tabIndexKey){
438 if(props.eventKey == props.tabIndexKey){
439 domProps.tabIndex = props.tabIndexValue;
440 }
441 }else if(props.pos == '0-0'){
442 domProps.tabIndex = props.tabIndexValue;
443 }
444 }
445
446
447
448 return (
449 <a ref={(el)=>{this.selectHandle=el}} pos={props.pos} title={typeof content === 'string' ? content : ''} {...domProps}>
450 {icon}{title}
451 </a>
452 );
453 };
454
455 let liProps = {};
456 if(props.liAttr){
457 liProps = Object.assign({},props.liAttr);
458 }
459 if (props.draggable) {
460 liProps.onDragEnter = this.onDragEnter;
461 liProps.onDragOver = this.onDragOver;
462 liProps.onDragLeave = this.onDragLeave;
463 liProps.onDrop = this.onDrop;
464 liProps.onDragEnd = this.onDragEnd;
465 }
466 let disabledCls = '';
467 let dragOverCls = '';
468 if (props.disabled) {
469 disabledCls = `${prefixCls}-treenode-disabled`;
470 } else if (props.dragOver) {
471 dragOverCls = 'drag-over';
472 } else if (props.dragOverGapTop) {
473 dragOverCls = 'drag-over-gap-top';
474 } else if (props.dragOverGapBottom) {
475 dragOverCls = 'drag-over-gap-bottom';
476 }
477
478 const filterCls = props.filterTreeNode ? (props.filterTreeNode(this) ? 'filter-node' : '') : '';
479
480 const noopSwitcher = () => {
481 const cls = {
482 [`${prefixCls}-switcher`]: true,
483 [`${prefixCls}-switcher-noop`]: true,
484 };
485 if (props.showLine) {
486 // console.log('line---------');
487 cls[`${prefixCls}-center_docu`] = !props.last;
488 cls[`${prefixCls}-bottom_docu`] = props.last;
489 } else {
490 cls[`${prefixCls}-noline_docu`] = true;
491 }
492 return <span className={classNames(cls)}></span>;
493 };
494 const selectedCls = props.selected?`${prefixCls}-treenode-selected`:'';
495 const focusedCls = props.focused ? `${prefixCls}-treenode-focused` : '';
496 const expandedCls = `${prefixCls}-treenode-${expandedState}`;
497 return (
498 <li {...liProps} style={props.style}
499 className={classNames(props.className, disabledCls, dragOverCls, filterCls,selectedCls,focusedCls,expandedCls) }
500 >
501 {canRenderSwitcher ? this.renderSwitcher(props, expandedState) : noopSwitcher()}
502 {props.checkable ? this.renderCheckbox(props) : null}
503 {selectHandle()}
504 {newChildren}
505 </li>
506 );
507 }
508}
509
510TreeNode.isTreeNode = 1;
511
512TreeNode.propTypes = {
513 prefixCls: PropTypes.string,
514 disabled: PropTypes.bool,
515 disableCheckbox: PropTypes.bool,
516 expanded: PropTypes.bool,
517 isLeaf: PropTypes.bool,
518 root: PropTypes.object,
519 onSelect: PropTypes.func,
520 openIcon: PropTypes.element,
521 closeIcon: PropTypes.element,
522 style: PropTypes.object,
523 className: PropTypes.string,
524 titleClass:PropTypes.string,
525 titleStyle:PropTypes.object,
526 switcherClass:PropTypes.string,
527 switcherStyle:PropTypes.object
528};
529
530TreeNode.defaultProps = {
531 title: defaultTitle,
532 tabIndexValue:0,
533 mustExpandable:false
534};
535
536export default TreeNode;
\No newline at end of file