├── demo ├── demo-vue2.html └── demo.js ├── LICENSE ├── src ├── tree.vue.css └── tree.vue.js └── README.md /demo/demo-vue2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue Tree 6 | 7 | 8 | 9 | 10 |
11 |

{{title}}

12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 weibangtuo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/tree.vue.css: -------------------------------------------------------------------------------- 1 | .vue-tree { 2 | font-size: 14px; 3 | min-height: 20px; 4 | -webkit-border-radius: 4px; 5 | -moz-border-radius: 4px; 6 | border-radius: 4px; 7 | } 8 | 9 | .vue-tree li { 10 | margin: 0; 11 | padding: 5px; 12 | position: relative; 13 | list-style: none; 14 | } 15 | 16 | .vue-tree li > span, 17 | .vue-tree li > i, 18 | .vue-tree li > a { 19 | line-height: 20px; 20 | vertical-align: middle; 21 | } 22 | 23 | .vue-tree li > a + a { 24 | margin-left: 5px; 25 | } 26 | 27 | .vue-tree li i.icon-open-state { 28 | font-size: 16px; 29 | } 30 | 31 | .vue-tree ul ul li:hover { 32 | background: rgba(0, 0, 0, .015) 33 | } 34 | 35 | .vue-tree li:after, .vue-tree li:before { 36 | content: ''; 37 | left: -18px; 38 | position: absolute; 39 | right: auto 40 | } 41 | 42 | .vue-tree li:before { 43 | border-left: 1px solid #999; 44 | bottom: 50px; 45 | height: 100%; 46 | top: -16px; 47 | width: 1px; 48 | } 49 | 50 | .vue-tree li:after { 51 | border-top: 1px solid #999; 52 | height: 20px; 53 | top: 17px; 54 | width: 22px 55 | } 56 | 57 | .vue-tree li span { 58 | display: inline-block; 59 | padding: 3px 5px; 60 | text-decoration: none; 61 | } 62 | 63 | .vue-tree > ul > li::after, .vue-tree > ul > li:before { 64 | border: 0 65 | } 66 | 67 | .vue-tree li:last-child::before { 68 | height: 34px 69 | } 70 | 71 | .vue-tree > ul { 72 | padding-left: 0 73 | } 74 | 75 | .vue-tree ul ul { 76 | padding-left: 24px; 77 | padding-top: 10px 78 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue Tree View Component 2 | 3 | Support `Vue.js` 2.0+ 4 | 5 | ## Usage 6 | 7 | Add the following required resources. 8 | 9 | ```html 10 | 11 | 12 | 13 | 14 | 15 | ``` 16 | 17 | Add the component in your vue view. 18 | 19 | ```html 20 |
21 | 22 |
23 | 37 | ``` 38 | 39 | ## Node Options 40 | 41 | `[opt]` means optional property. 42 | 43 | ```javascript 44 | { 45 | name: 'Node Name', 46 | title: 'Node Tag title attr', 47 | isParent: true, // Requested for parent node 48 | isOpen: false, // [opt] Control node to fold or unfold 49 | icon: 'fa fa-folder', //[opt] Icon class name 50 | openedIcon: 'fa fa-folder-open', // [opt] For parent. Show when isOpen == true, show icon if it's null or empty 51 | closedIcon: 'fa fa-folder', // [opt] For parent. Show when isOpen != true, show icon if it's null or empty 52 | children: [], // Requested for parent node 53 | buttons: [ // [opt] 54 | { 55 | title: 'icon button tag title attr', //[opt] 56 | icon: 'fa fa-edit', 57 | click: function (node) { //[opt] 58 | // 59 | } 60 | } 61 | //... 62 | ], 63 | showLoading: false, // [opt] For parent, when `node.showLoading && node._loading` and node is opened then show loading icon 64 | onOpened: function (node) {}, // [opt] 65 | onClosed: function (node) {} // [opt] 66 | } 67 | ``` 68 | 69 | 70 | 71 | 72 | ## License 73 | 74 | Copyright (c) 2016 weibangtuo. Under MIT License. 75 | -------------------------------------------------------------------------------- /src/tree.vue.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | var VueTreeItem = Vue.extend({ 4 | template: '
  • ' + 5 | '' + 6 | '' + 7 | ' {{node.name}}' + 8 | '' + 9 | '' + 13 | '
  • ', 14 | props: { 15 | node: { 16 | type: Object 17 | } 18 | }, 19 | methods: { 20 | showIcon: function (node) { 21 | return node.icon || node.openedIcon || node.closedIcon; 22 | }, 23 | nodeClass: function (node) { 24 | if (node.isOpen) { 25 | return node.openedIcon || node.icon; 26 | } else { 27 | return node.closedIcon || node.icon; 28 | } 29 | }, 30 | toggle: function (node) { 31 | if (node.hasOwnProperty('isOpen')) { 32 | node.isOpen = !node.isOpen; 33 | } else { 34 | Vue.set(node, 'isOpen', true); 35 | } 36 | }, 37 | btnClick: function (btn, node) { 38 | if (typeof btn.click === 'function') { 39 | btn.click(node); 40 | } 41 | } 42 | }, 43 | watch: { 44 | 'node.isOpen': function (val) { 45 | if (!this.node.hasOwnProperty('_loading')) { 46 | Vue.set(this.node, '_loading', false); 47 | } 48 | if (val) { 49 | if (typeof this.node.onOpened === 'function') { 50 | this.node.onOpened(this.node); 51 | } 52 | } else { 53 | if (typeof this.node.onClosed === 'function') { 54 | this.node.onClosed(this.node); 55 | } 56 | } 57 | } 58 | } 59 | }); 60 | Vue.component('vue-tree-item', VueTreeItem); 61 | 62 | var VueTree = Vue.extend({ 63 | template: '
    ', 66 | props: { 67 | option: { 68 | type: Object 69 | } 70 | }, 71 | components: { 72 | 'tree-item': VueTreeItem 73 | } 74 | }); 75 | Vue.component('vue-tree', VueTree); 76 | })(); -------------------------------------------------------------------------------- /demo/demo.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | var addNode = function (node) { 4 | node.isOpen = true; 5 | node.children.push({ 6 | name: 'child node ' + node.children.length, 7 | parent: node, 8 | isParent: true, 9 | children: [], 10 | buttons: [ 11 | { 12 | title: 'Add', 13 | icon: 'fa fa-plus', 14 | click: addNode 15 | }, { 16 | title: 'Delete', 17 | icon: 'fa fa-trash', 18 | click: function (node) { 19 | node.parent.children.splice(node.parent.children.indexOf(node), 1); 20 | } 21 | } 22 | ] 23 | }); 24 | }; 25 | new Vue({ 26 | el: '#demo', 27 | data: { 28 | title: 'Vue Tree Demo', 29 | option: { 30 | root: { 31 | name: 'Root Node', 32 | isParent: true, 33 | isOpen: true, 34 | openedIcon: 'fa fa-folder-open-o', 35 | closedIcon: 'fa fa-folder-o', 36 | children: [ 37 | { 38 | name: 'Level1' 39 | }, 40 | { 41 | name: 'Level1 with icon', 42 | icon: 'fa fa-cube', 43 | title: '192.168.89.0' 44 | }, 45 | { 46 | name: 'Edit node', 47 | buttons: [ 48 | { 49 | title: 'Edit', 50 | icon: 'fa fa-edit', 51 | click: function (node) { 52 | node.name = prompt('Editing node name, require string', node.name) || node.name; 53 | } 54 | } 55 | ] 56 | }, 57 | { 58 | name: 'Level1 has children', 59 | icon: 'fa fa-folder', 60 | openedIcon: 'fa fa-folder-open', 61 | isOpen: false, 62 | isParent: true, 63 | children: [ 64 | { 65 | name: 'level2 - 1', 66 | icon: 'fa fa-file' 67 | }, 68 | { 69 | name: 'level2 - 2', 70 | icon: 'fa fa-file' 71 | }, 72 | { 73 | name: 'level2 - 3', 74 | icon: 'fa fa-file' 75 | } 76 | ] 77 | }, 78 | { 79 | name: 'Level1 add node', 80 | isParent: true, 81 | children: [], 82 | buttons: [ 83 | { 84 | title: 'Add', 85 | icon: 'fa fa-plus', 86 | click: function (node) { 87 | node.isOpen = true; 88 | node.children.push({ 89 | name: 'Level2 node ' + node.children.length, 90 | parent: node, 91 | buttons: [ 92 | { 93 | title: 'Delete', 94 | icon: 'fa fa-trash', 95 | click: function (node) { 96 | node.parent.children.splice(node.parent.children.indexOf(node), 1); 97 | } 98 | } 99 | ] 100 | }); 101 | } 102 | } 103 | ] 104 | }, 105 | { 106 | name: 'Level1-addNode', 107 | isParent: true, 108 | children: [], 109 | buttons: [ 110 | { 111 | title: 'Add', 112 | icon: 'fa fa-plus', 113 | click: addNode 114 | } 115 | ] 116 | }, 117 | { 118 | name: 'Level1 Ajax', 119 | isParent: true, 120 | children: [], 121 | showLoading: true, // if (node.showLoading && node._loading) then show loading icon 122 | onOpened: function (node) { 123 | if (!node._loading) { 124 | Vue.set(node, 'children', []); // Clean old data 125 | node._loading = true; // Start Ajax 126 | setTimeout(function () { // 127 | node._loading = false; //Finish Ajax 128 | for (var i = 1; i < 6; i++) { 129 | node.children.push({name: 'Ajax Node ' + i}); 130 | } 131 | }, 2000); 132 | } 133 | }, 134 | onClosed: function () { 135 | // NOOP 136 | } 137 | } 138 | ] 139 | } 140 | } 141 | } 142 | }); 143 | })(); --------------------------------------------------------------------------------