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 ===...