123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- <template>
- <div
- class="el-tree"
- :class="{
- 'el-tree--highlight-current': highlightCurrent,
- 'is-dragging': !!dragState.draggingNode,
- 'is-drop-not-allow': !dragState.allowDrop,
- 'is-drop-inner': dragState.dropType === 'inner'
- }"
- role="tree"
- >
- <el-tree-node
- v-for="child in root.childNodes"
- :node="child"
- :props="props"
- :render-after-expand="renderAfterExpand"
- :show-checkbox="showCheckbox"
- :key="getNodeKey(child)"
- :render-content="renderContent"
- @node-expand="handleNodeExpand">
- </el-tree-node>
- <div class="el-tree__empty-block" v-if="isEmpty">
- <span class="el-tree__empty-text">{{ emptyText }}</span>
- </div>
- <div
- v-show="dragState.showDropIndicator"
- class="el-tree__drop-indicator"
- ref="dropIndicator">
- </div>
- </div>
- </template>
- <script>
- import TreeStore from './model/tree-store';
- import { getNodeKey, findNearestComponent } from './model/util';
- import ElTreeNode from './tree-node.vue';
- import {t} from 'element-ui/src/locale';
- import emitter from 'element-ui/src/mixins/emitter';
- import { addClass, removeClass } from 'element-ui/src/utils/dom';
- export default {
- name: 'ElTree',
- mixins: [emitter],
- components: {
- ElTreeNode
- },
- data() {
- return {
- store: null,
- root: null,
- currentNode: null,
- treeItems: null,
- checkboxItems: [],
- dragState: {
- showDropIndicator: false,
- draggingNode: null,
- dropNode: null,
- allowDrop: true
- }
- };
- },
- props: {
- data: {
- type: Array
- },
- emptyText: {
- type: String,
- default() {
- return t('el.tree.emptyText');
- }
- },
- renderAfterExpand: {
- type: Boolean,
- default: true
- },
- nodeKey: String,
- checkStrictly: Boolean,
- defaultExpandAll: Boolean,
- expandOnClickNode: {
- type: Boolean,
- default: true
- },
- checkOnClickNode: Boolean,
- checkDescendants: {
- type: Boolean,
- default: false
- },
- autoExpandParent: {
- type: Boolean,
- default: true
- },
- defaultCheckedKeys: Array,
- defaultExpandedKeys: Array,
- currentNodeKey: [String, Number],
- renderContent: Function,
- showCheckbox: {
- type: Boolean,
- default: false
- },
- draggable: {
- type: Boolean,
- default: false
- },
- allowDrag: Function,
- allowDrop: Function,
- props: {
- default() {
- return {
- children: 'children',
- label: 'label',
- disabled: 'disabled'
- };
- }
- },
- lazy: {
- type: Boolean,
- default: false
- },
- highlightCurrent: Boolean,
- load: Function,
- filterNodeMethod: Function,
- accordion: Boolean,
- indent: {
- type: Number,
- default: 18
- },
- iconClass: String
- },
- computed: {
- children: {
- set(value) {
- this.data = value;
- },
- get() {
- return this.data;
- }
- },
- treeItemArray() {
- return Array.prototype.slice.call(this.treeItems);
- },
- isEmpty() {
- const { childNodes } = this.root;
- return !childNodes || childNodes.length === 0 || childNodes.every(({visible}) => !visible);
- }
- },
- watch: {
- defaultCheckedKeys(newVal) {
- this.store.setDefaultCheckedKey(newVal);
- },
- defaultExpandedKeys(newVal) {
- this.store.defaultExpandedKeys = newVal;
- this.store.setDefaultExpandedKeys(newVal);
- },
- data(newVal) {
- this.store.setData(newVal);
- },
- checkboxItems(val) {
- Array.prototype.forEach.call(val, (checkbox) => {
- checkbox.setAttribute('tabindex', -1);
- });
- },
- checkStrictly(newVal) {
- this.store.checkStrictly = newVal;
- }
- },
- methods: {
- filter(value) {
- if (!this.filterNodeMethod) throw new Error('[Tree] filterNodeMethod is required when filter');
- this.store.filter(value);
- },
- getNodeKey(node) {
- return getNodeKey(this.nodeKey, node.data);
- },
- getNodePath(data) {
- if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in getNodePath');
- const node = this.store.getNode(data);
- if (!node) return [];
- const path = [node.data];
- let parent = node.parent;
- while (parent && parent !== this.root) {
- path.push(parent.data);
- parent = parent.parent;
- }
- return path.reverse();
- },
- getCheckedNodes(leafOnly, includeHalfChecked) {
- return this.store.getCheckedNodes(leafOnly, includeHalfChecked);
- },
- getCheckedKeys(leafOnly) {
- return this.store.getCheckedKeys(leafOnly);
- },
- getCurrentNode() {
- const currentNode = this.store.getCurrentNode();
- return currentNode ? currentNode.data : null;
- },
- getCurrentKey() {
- if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in getCurrentKey');
- const currentNode = this.getCurrentNode();
- return currentNode ? currentNode[this.nodeKey] : null;
- },
- setCheckedNodes(nodes, leafOnly) {
- if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCheckedNodes');
- this.store.setCheckedNodes(nodes, leafOnly);
- },
- setCheckedKeys(keys, leafOnly) {
- if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCheckedKeys');
- this.store.setCheckedKeys(keys, leafOnly);
- },
- setChecked(data, checked, deep) {
- this.store.setChecked(data, checked, deep);
- },
- getHalfCheckedNodes() {
- return this.store.getHalfCheckedNodes();
- },
- getHalfCheckedKeys() {
- return this.store.getHalfCheckedKeys();
- },
- setCurrentNode(node) {
- if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentNode');
- this.store.setUserCurrentNode(node);
- },
- setCurrentKey(key) {
- if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentKey');
- this.store.setCurrentNodeKey(key);
- },
- getNode(data) {
- return this.store.getNode(data);
- },
- remove(data) {
- this.store.remove(data);
- },
- append(data, parentNode) {
- this.store.append(data, parentNode);
- },
- insertBefore(data, refNode) {
- this.store.insertBefore(data, refNode);
- },
- insertAfter(data, refNode) {
- this.store.insertAfter(data, refNode);
- },
- handleNodeExpand(nodeData, node, instance) {
- this.broadcast('ElTreeNode', 'tree-node-expand', node);
- this.$emit('node-expand', nodeData, node, instance);
- },
- updateKeyChildren(key, data) {
- if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in updateKeyChild');
- this.store.updateChildren(key, data);
- },
- initTabIndex() {
- this.treeItems = this.$el.querySelectorAll('.is-focusable[role=treeitem]');
- this.checkboxItems = this.$el.querySelectorAll('input[type=checkbox]');
- const checkedItem = this.$el.querySelectorAll('.is-checked[role=treeitem]');
- if (checkedItem.length) {
- checkedItem[0].setAttribute('tabindex', 0);
- return;
- }
- this.treeItems[0] && this.treeItems[0].setAttribute('tabindex', 0);
- },
- handleKeydown(ev) {
- const currentItem = ev.target;
- if (currentItem.className.indexOf('el-tree-node') === -1) return;
- const keyCode = ev.keyCode;
- this.treeItems = this.$el.querySelectorAll('.is-focusable[role=treeitem]');
- const currentIndex = this.treeItemArray.indexOf(currentItem);
- let nextIndex;
- if ([38, 40].indexOf(keyCode) > -1) { // up、down
- ev.preventDefault();
- if (keyCode === 38) { // up
- nextIndex = currentIndex !== 0 ? currentIndex - 1 : 0;
- } else {
- nextIndex = (currentIndex < this.treeItemArray.length - 1) ? currentIndex + 1 : 0;
- }
- this.treeItemArray[nextIndex].focus(); // 选中
- }
- if ([37, 39].indexOf(keyCode) > -1) { // left、right 展开
- ev.preventDefault();
- currentItem.click(); // 选中
- }
- const hasInput = currentItem.querySelector('[type="checkbox"]');
- if ([13, 32].indexOf(keyCode) > -1 && hasInput) { // space enter选中checkbox
- ev.preventDefault();
- hasInput.click();
- }
- }
- },
- created() {
- this.isTree = true;
- this.store = new TreeStore({
- key: this.nodeKey,
- data: this.data,
- lazy: this.lazy,
- props: this.props,
- load: this.load,
- currentNodeKey: this.currentNodeKey,
- checkStrictly: this.checkStrictly,
- checkDescendants: this.checkDescendants,
- defaultCheckedKeys: this.defaultCheckedKeys,
- defaultExpandedKeys: this.defaultExpandedKeys,
- autoExpandParent: this.autoExpandParent,
- defaultExpandAll: this.defaultExpandAll,
- filterNodeMethod: this.filterNodeMethod
- });
- this.root = this.store.root;
- let dragState = this.dragState;
- this.$on('tree-node-drag-start', (event, treeNode) => {
- if (typeof this.allowDrag === 'function' && !this.allowDrag(treeNode.node)) {
- event.preventDefault();
- return false;
- }
- event.dataTransfer.effectAllowed = 'move';
- // wrap in try catch to address IE's error when first param is 'text/plain'
- try {
- // setData is required for draggable to work in FireFox
- // the content has to be '' so dragging a node out of the tree won't open a new tab in FireFox
- event.dataTransfer.setData('text/plain', '');
- } catch (e) {}
- dragState.draggingNode = treeNode;
- this.$emit('node-drag-start', treeNode.node, event);
- });
- this.$on('tree-node-drag-over', (event, treeNode) => {
- const dropNode = findNearestComponent(event.target, 'ElTreeNode');
- const oldDropNode = dragState.dropNode;
- if (oldDropNode && oldDropNode !== dropNode) {
- removeClass(oldDropNode.$el, 'is-drop-inner');
- }
- const draggingNode = dragState.draggingNode;
- if (!draggingNode || !dropNode) return;
- let dropPrev = true;
- let dropInner = true;
- let dropNext = true;
- let userAllowDropInner = true;
- if (typeof this.allowDrop === 'function') {
- dropPrev = this.allowDrop(draggingNode.node, dropNode.node, 'prev');
- userAllowDropInner = dropInner = this.allowDrop(draggingNode.node, dropNode.node, 'inner');
- dropNext = this.allowDrop(draggingNode.node, dropNode.node, 'next');
- }
- event.dataTransfer.dropEffect = dropInner ? 'move' : 'none';
- if ((dropPrev || dropInner || dropNext) && oldDropNode !== dropNode) {
- if (oldDropNode) {
- this.$emit('node-drag-leave', draggingNode.node, oldDropNode.node, event);
- }
- this.$emit('node-drag-enter', draggingNode.node, dropNode.node, event);
- }
- if (dropPrev || dropInner || dropNext) {
- dragState.dropNode = dropNode;
- }
- if (dropNode.node.nextSibling === draggingNode.node) {
- dropNext = false;
- }
- if (dropNode.node.previousSibling === draggingNode.node) {
- dropPrev = false;
- }
- if (dropNode.node.contains(draggingNode.node, false)) {
- dropInner = false;
- }
- if (draggingNode.node === dropNode.node || draggingNode.node.contains(dropNode.node)) {
- dropPrev = false;
- dropInner = false;
- dropNext = false;
- }
- const targetPosition = dropNode.$el.getBoundingClientRect();
- const treePosition = this.$el.getBoundingClientRect();
- let dropType;
- const prevPercent = dropPrev ? (dropInner ? 0.25 : (dropNext ? 0.45 : 1)) : -1;
- const nextPercent = dropNext ? (dropInner ? 0.75 : (dropPrev ? 0.55 : 0)) : 1;
- let indicatorTop = -9999;
- const distance = event.clientY - targetPosition.top;
- if (distance < targetPosition.height * prevPercent) {
- dropType = 'before';
- } else if (distance > targetPosition.height * nextPercent) {
- dropType = 'after';
- } else if (dropInner) {
- dropType = 'inner';
- } else {
- dropType = 'none';
- }
- const iconPosition = dropNode.$el.querySelector('.el-tree-node__expand-icon').getBoundingClientRect();
- const dropIndicator = this.$refs.dropIndicator;
- if (dropType === 'before') {
- indicatorTop = iconPosition.top - treePosition.top;
- } else if (dropType === 'after') {
- indicatorTop = iconPosition.bottom - treePosition.top;
- }
- dropIndicator.style.top = indicatorTop + 'px';
- dropIndicator.style.left = (iconPosition.right - treePosition.left) + 'px';
- if (dropType === 'inner') {
- addClass(dropNode.$el, 'is-drop-inner');
- } else {
- removeClass(dropNode.$el, 'is-drop-inner');
- }
- dragState.showDropIndicator = dropType === 'before' || dropType === 'after';
- dragState.allowDrop = dragState.showDropIndicator || userAllowDropInner;
- dragState.dropType = dropType;
- this.$emit('node-drag-over', draggingNode.node, dropNode.node, event);
- });
- this.$on('tree-node-drag-end', (event) => {
- const { draggingNode, dropType, dropNode } = dragState;
- event.preventDefault();
- event.dataTransfer.dropEffect = 'move';
- if (draggingNode && dropNode) {
- const draggingNodeCopy = { data: draggingNode.node.data };
- if (dropType !== 'none') {
- draggingNode.node.remove();
- }
- if (dropType === 'before') {
- dropNode.node.parent.insertBefore(draggingNodeCopy, dropNode.node);
- } else if (dropType === 'after') {
- dropNode.node.parent.insertAfter(draggingNodeCopy, dropNode.node);
- } else if (dropType === 'inner') {
- dropNode.node.insertChild(draggingNodeCopy);
- }
- if (dropType !== 'none') {
- this.store.registerNode(draggingNodeCopy);
- }
- removeClass(dropNode.$el, 'is-drop-inner');
- this.$emit('node-drag-end', draggingNode.node, dropNode.node, dropType, event);
- if (dropType !== 'none') {
- this.$emit('node-drop', draggingNode.node, dropNode.node, dropType, event);
- }
- }
- if (draggingNode && !dropNode) {
- this.$emit('node-drag-end', draggingNode.node, null, dropType, event);
- }
- dragState.showDropIndicator = false;
- dragState.draggingNode = null;
- dragState.dropNode = null;
- dragState.allowDrop = true;
- });
- },
- mounted() {
- this.initTabIndex();
- this.$el.addEventListener('keydown', this.handleKeydown);
- },
- updated() {
- this.treeItems = this.$el.querySelectorAll('[role=treeitem]');
- this.checkboxItems = this.$el.querySelectorAll('input[type=checkbox]');
- }
- };
- </script>
|