Skip to content

Instantly share code, notes, and snippets.

@ajmas ajmas/Demo.vue
Last active Dec 26, 2017

Embed
What would you like to do?
dabeng/OrgChart adapted to work with Vue Single File Component
<template>
<div>
<div id="orgchart" class="orgchart">
<node :model="nodeData" v-on:node-click="nodeClick"></node>
</div>
<p><button id="add">Add</button><button id="remove">Remove</button></p>
<p>(You can double click on an item to turn it into a folder.)</p>
<ul id="demo">
<item :model="nodeData"></item>
</ul>
</div>
</template>
<script>
import node from './Node';
import item from './Item';
// demo data
var sourceData = {
id:'0', name: 'A My Tree', title: 'mytitle',
children: [
{ id:'1', name: 'B hello', children: []},
{ id:'2', name: 'C wat', title: 'mytitle', },
{
id:'3', name: 'D child folder',
children: [
{
id:'4', name: 'E child folder',
children: [
{ id:'5', name: 'F hello' },
{ id:'6', name: 'G wat' }
]
},
{ id:'7', name: 'H hello' },
{ id:'8', name: 'I wat' },
{
id:'9', name: 'J child folder',
children: [
{ id:'10', name: 'K hello' },
{ id:'11', name: 'L wat' }
]
}
]
}
]
}
function findNodeInTree(nodeId, node) {
if (node.id === nodeId) {
return node;
} else {
if (node.children) {
var children = node.children;
for (let i=0; i<children.length; i++) {
var nodeMatch = findNodeInTree(nodeId, children[i]);
if (nodeMatch) {
return nodeMatch;
}
}
}
return undefined;
}
}
export default {
data: function () {
return {
nodeData: sourceData
}
},
components: { node, item },
mounted () {
add.onclick = function() {
if (this.selectedNode && this.selectedNode.model) {
this.selectedNode.addChild({ id: Date.now(), name: 'untiled' });
}
}.bind(this);
remove.onclick = function() {
if (this.selectedNode) {
this.selectedNode.remove();
}
}.bind(this);
},
methods: {
nodeClick: function (event) {
this.selectedNode = event;
const rootNode = event.getRootNode();
rootNode.select(event);
}
}
}
</script>
<style>
body {
font-family: Menlo, Consolas, monospace;
color: #444;
}
table {
text-align: center;
}
.orgchart {
margin-top: 50px;
background-image: none !important;
background-color: white;
}
.orgchart .line div {
border: 1px solid rgb(62, 111, 184);
}
.orgchart .node {
padding-top: 0;
padding-bottom: 0;
border-top: 0;
border-bottom: 0;
}
.orgchart .node .title {
background: rgb(62, 111, 184);
}
.orgchart .node.selected .title {
background: rgb(235, 152, 28);
}
.orgchart .node .content {
border-color: rgb(62, 111, 184);
}
.orgchart .node.selected .content {
border-color: rgb(235, 152, 28);
}
.orgchart .nodes td>.node:before {
content: '';
border: 1px solid rgb(62, 111, 184);;
}
.orgchart .nodes.single td>.node:before {
margin-left: 1px;
}
.orgchart td>.node.has-children:after {
content: '';
border: 1px solid rgb(62, 111, 184);
}
td {
vertical-align: top;
}
</style>
<!-- An adaption of work done in this jsfiddle: https://jsfiddle.net/h5jr4tcv/9/ as Single File Component -->
<template>
<li>
<div
:class="{bold: isFolder}"
@click="toggle"
@dblclick="changeType">
{{model.name}}
<span v-if="isFolder">[{{open ? '-' : '+'}}]</span>
</div>
<ul v-show="open" v-if="isFolder">
<item
class="item"
v-for="(model, index) in model.children"
:model="model"
:key="index">
</item>
<li class="add" @click="addChild">+</li>
</ul>
</li>
</template>
<script>
import Vue from 'vue';
export default {
name: 'item',
props: {
model: Object
},
data: function() {
return {
open: false
};
},
computed: {
isFolder: function() {
return this.model.children && this.model.children.length;
}
},
methods: {
toggle: function() {
if (this.isFolder) {
this.open = !this.open;
}
},
changeType: function() {
if (!this.isFolder) {
Vue.set(this.model, "children", []);
this.addChild();
this.open = true;
}
},
addChild: function() {
if (this.model) {
this.model.children.push({
name: "new stuff"
});
}
}
}
};
</script>
<style>
.item {
cursor: pointer;
}
.bold {
font-weight: bold;
}
ul {
padding-left: 1em;
line-height: 1.5em;
list-style-type: circle;
}
</style>
<!-- An adaption of work done in this jsfiddle: https://jsfiddle.net/h5jr4tcv/9/ as Single File Component -->
<template>
<table>
<tr>
<td :colspan="colspan">
<div class="node" v-on:click="nodeClick" v-bind:class="{ 'has-children': hasChildren, 'selected': isSelected }">
<div class="title">{{model.name}}</div>
<div class="content"></div>
<i class="edge verticalEdge bottomEdge fa"></i>
</div>
</td>
</tr>
<tr v-if="hasChildren" class="line">
<td :colspan="colspan"><div v-bind:style="style"></div></td>
</tr>
<tr v-if="hasChildren" class="nodes" v-bind:class="{ 'single': colspan == 1 }">
<td v-for="(model,index) in model.children" :key="index">
<childnode :model="model" :index="index" @childrenWidth="childrenWidth" :dblclick="addChild" v-on:node-click="nodeClick" :select="select"></childnode>
</td>
</tr>
</table>
</template>
<script>
import Vue from 'vue';
export default {
name: 'childnode',
props: {
selected: false,
model: Object,
index: Number
},
data: function() {
var d = {};
if (this.model.children) {
this.model.children.forEach(function(c, index) {
d["line_" + index] = 130;
});
}
return d;
},
mounted: function() {
this.$emit("childrenWidth", this.index, this.$el.offsetWidth);
},
updated: function() {
this.$emit("childrenWidth", this.index, this.$el.offsetWidth);
},
destroyed: function() {
this.$emit("childrenWidth", this.index);
},
methods: {
childrenWidth: function(cIndex, cWidth) {
this["line_" + cIndex] = cWidth;
},
addChild: function(data) {
if (!this.model.children) {
Vue.set(this.model, "children", []);
}
this.model.children.push(data);
},
remove: function(node) {
if (node) {
const nodeId = node.model.id;
if (!nodeId) {
console.warn('functionality requires nodes to have an id');
return;
}
const children = this.model.children;
if (children) {
let childIdx = -1;
for(let i=0; i<children.length; i++) {
if (children[i].id === nodeId) {
childIdx = i;
break;
}
}
if(childIdx > -1) {
this.model.children.splice(childIdx,1);
}
}
} else if (this.$parent) {
// ensure we only try removing nodes with parents
const myComponentType = this.constructor.options.name;
if (this.$parent.constructor.options.name === myComponentType) {
this.$parent.remove(this);
}
}
},
click: function(event) {
this.$emit('node-click', this);
},
nodeClick: function (event) {
// Not sure if this is the best way, but it works.
// Still new at vue development
if (event instanceof MouseEvent) {
this.$emit('node-click', this);
} else {
this.$emit('node-click', event);
}
},
getRootNode() {
const myComponentType = this.constructor.options.name;
if (this.$parent.constructor.options.name === myComponentType) {
return this.$parent.getRootNode();
} else {
return this;
}
},
select(node) {
Vue.set(this.model, "selected", (node === this));
if (this.$children) {
const children = this.$children;
for (let i=0; i<children.length; i++) {
children[i].select(node);
}
}
}
},
computed: {
style: function() {
var left = (this["line_0"] || 130) / 2;
var right = (this["line_" + (this.model.children.length - 1)] || 130) / 2;
return { marginLeft: left + "px", marginRight: right + "px" };
},
colspan: function() {
return (this.model.children && this.model.children.length) || 1;
},
hasChildren: function() {
return this.model.children && this.model.children.length > 0;
},
isSelected: function () {
return this.model.selected;
}
}
};
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.