├── 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 | '' +
10 | ' ' +
11 | '' +
12 | '
' +
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 | })();
--------------------------------------------------------------------------------