Vue - Tree Component - flat view
by Ben Clayton
HTML
<script src="https://unpkg.com/vue"></script>
<div class="tree" id="hotdirectory">
<button @click="view('tree')">
Tree
</button>
<button @click="view('icon')">
Icon
</button>
<button @click="sortby('name')">
Name {{sortdir('name')}}
</button>
<button @click="sortby('date')">
Date {{sortdir('date')}}
</button>
<hr>
<div class="scrollarea">
<vue-folder v-on:choose="choose()" :settings="settings" :path="path" :files="files"></vue-folder>
</div>
<button class="but-choose" @click="choose()">
Choose
</button>
<button class="but-cancel" @click="cancel()">
Cancel
</button>
</div>
<script type="text/x-template" id="folder-template">
<div>
<!-- ICON VIEW -->
<div v-if="settings.view=='icon'" class="iconview">
<div v-if="leafFiles().length">
<hr>
<div v-bind:class="{folder:1, open: pfile.open, nonEmptyFolder: isNonEmptyFolder(pfile) }" @click="click(pfile)"></div>
{{ path }}<br/>
<span v-if="pfile.open" v-for="file in leafFiles()">
<a @click="click(file)" @dblclick="dblclick(file)" v-bind:class="{
folder: isFolder(file),
nonEmptyFolder: isNonEmptyFolder(file),
open: file.open,
selected:file.selected
}">
<span class="iconwrapper">
<div class="largeicon" :title="file.name"></div>
</span>
</a>
</span>
</div>
<span v-for="file in files">
<div class="children">
<vue-folder v-on:choose="choose()" :settings="settings" :pfile="file" :path="getPath(file)" :files="file.children"></vue-folder>
</div>
</span>
</div>
<!-- TREE VIEW -->
<ul v-if="settings.view=='tree'" class="treeview">
<li v-for="file in sortedFiles">
<a @click="click(file)" @dblclick="dblclick(file)" v-bind:class="{ leaf:isLeaf(file), folder: isFolder(file), nonEmptyFolder: isNonEmptyFolder(file), open: file.open, selected:isSelected(file) }" :title="file.name">
{{ file.name }}
</a><div class="date">{{...
SCSS
#hotdirectory.tree {
border: 3px solid gray;
width: 450px;
height: 600px;
overflow: none;
padding: 20px;
margin-top: 20px;
.scrollarea {
height: 550px;
overflow: auto;
}
.but-cancel,.but-choose {
margin-top: 10px;
float: right;
}
.iconview {
.folder {
width: 20px;
height: 17px;
background-color: yellow;
display: inline-block;
&::before {
content: '';
width: 20px;
height: 15px;
display: inline-block;
margin-right: 3px;
text-align: center;
}
&.nonEmptyFolder::before {
vertical-align: top;
position: relative;
content: '+';
}
&.nonEmptyFolder.open::before {
content: '-';
}
}
.largeicon {
width: 40px;
height: 40px;
background-color: gray;
}
.iconwrapper {
width: 45px;
display: inline-block;
margin: 5px;
}
.selected {
.largeicon {
background-color: blue;
}
}
}
ul.treeview {
list-style: none;
padding-left: 5px;
padding-right : 5px;
li {
padding-left: 16px;
position: relative;
box-sizing: border-box;
&::before {
content: '';
height: 1px;
width: 10px;
background-color: #333;
position: absolute;
top: 10px;
left: 0;
margin: auto;
}
&::after {
content: '';
width: 1px;
height: 100%;
background-color: #333;
position: absolute;
top: 0;
bottom: 0;
left: 0;
}
&:last-child {
&::after {
height: 10px;
}
}
}
.date {
float: right;
display: inline-block;
}
a {
cursor: pointer;
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: calc(100% - 75px);
&::before {
content: '';
width: 20px;
height: 15px;
background-color: gray;
display:...
TypeScript
Vue.component('vue-folder', {
props: ["files", "path","pfile","settings"],
template: "#folder-template",
methods: {
click: (clickedfile) => {
clickedfile.open = 1 - clickedfile.open;
console.log(clickedfile.name, clickedfile.open);
if (this.settings.selected === clickedfile){
this.settings.selected = null;
} else {
this.settings.selected = clickedfile
}
},
dblclick: (clickedfile) => {
console.log("DBL ", clickedfile.name);
this.$emit("choose");
},
isNonEmptyFolder: (displayfile) => {
return displayfile.children && displayfile.children.length > 0;
},
isFolder: (displayfile) => {
return displayfile.children;
},
isLeaf: (displayfile) => {
return !displayfile.children;
},
isSelected: (displayfile) => {
return this.settings.selected === displayfile;
},
getState: (displayfile) => {
if (this.isFolder(displayfile))
return displayfile.open ? "+" : "-";
else
return ""
},
getPath: (displayfile) => {
return this.path + ' / ' + displayfile.name
},
choose: () =>{
console.log("emit ");
this.$emit("choose");
},
leafFiles: () => {
const leafFiles = [];
if (this.files){
this.sortedFiles.forEach(file => {
if (typeof file.children === 'undefined') {
leafFiles.push(file);
}
});
}
return leafFiles;
}
},
computed: {
sortedFiles: () => {
if ( ! this.files ) return;
function comparename(a, b) {
// if (a.children) return -1;
// if (b.children) return 1;
if (a.name < b.name)
return -1;
if (a.name > b.name)
return 1;
return 0;
}
function comparedate(a, b) {
a.dateobj = a.dateobj | new Date(a.date).getTime();
b.dateobj = b.dateobj | new Date(b.date).getTime();
if (a.dateobj < b.dateobj)
return -1;
if (a.dateobj > b.dateobj)
return 1;
return 0;
}
if (this.settings.sort ===...