CanJS File Navigator (Advanced)
Finished version of the CanJS File Navigator Guide (Advanced)
by donejs
HTML
<script src="https://unpkg.com/can@3/dist/global/can.all.js"></script>
<script type="text/stache" id="app-template">
<a-folder folder:from="this" isOpen:from="true" />
</script>
<script type="text/stache" id="folder-template">
<span on:click="toggleOpen()">{{folder.name}}</span>
{{#if isOpen}}
{{#if entitiesPromise.isPending}}
<div class="loading">Loading</div>
{{else}}
<ul>
{{#each entitiesPromise.value}}
<li class="{{type}} {{#if hasChildren}}hasChildren{{/if}}">
{{#eq type 'file'}}
📝 <span>{{name}}</span>
{{else}}
📁 <a-folder folder:from="this" />
{{/eq}}
</li>
{{/each}}
</ul>
{{/if}}
{{/if}}
</script>
CSS
body {
padding: 10px;
}
@import url('https://fonts.googleapis.com/css?family=Roboto+Mono:400,700');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Roboto Mono", "Lucida Console", Monaco, monospace;
font-size: 14px;
color: #000;
}
ul {
list-style: none;
}
li {
padding-left: 20px;
position: relative;
line-height: 2em;
}
li:before {
position: absolute;
left: 5px;
top: -2px;
content: '';
display: block;
height: 1em;
border-bottom: 1px solid #bbb;
width: 10px;
}
li:after {
position: absolute;
left: 5px;
bottom: 0;
content: '';
display: block;
border-left: 1px solid #bbb;
height: 100%;
}
.loading {
color: black;
padding-left: 20px;
padding-top: 10px;
padding-bottom: 10px;
}
.loading:after {
content: "...";
}
.hasChildren > a-folder > span, .hasChildren > span {
transition: all .1s ease-in-out;
cursor: pointer;
border-bottom: 1px dotted #000;
}
.hasChildren > a-folder > span:hover, .hasChildren > span:hover {
opacity: .5;
}
JavaScript
// Stores the next entity id to use.
var entityId = 1;
// Returns an array of entities for the given `parentId`.
// Makes sure the `depth` of entities doesn’t exceed 5.
var makeEntities = function(parentId, depth) {
if (depth > 5) {
return [];
}
// The number of entities to create.
var entitiesCount = can.fixture.rand(10);
// The array of entities we will return.
var entities = [];
for (var i = 0; i < entitiesCount; i++) {
// The id for this entity
var id = "" + (entityId++);
// If the entity is a folder or file
var isFolder = Math.random() > 0.3;
// The children for this folder.
var children = isFolder ? makeEntities(id, depth+1) : [];
var entity = {
id: id,
name: (isFolder ? "Folder" : "File") + " " + id,
parentId: parentId,
type: (isFolder ? "folder" : "file"),
hasChildren: children.length > 0
};
entities.push(entity);
// Add the children of a folder
[].push.apply(entities, children)
}
return entities;
};
// Make the entities for the demo
var entities = makeEntities("0", 0);
// Add them to a client-like DB store
var entitiesStore = can.fixture.store(entities);
// Trap requests to /api/entities to read items from the entities store.
can.fixture("/api/entities", entitiesStore);
// Make requests to /api/entities take 1 second
can.fixture.delay = 1000;
var Entity = can.DefineMap.extend({
id: "string",
name: "string",
parentId: "string",
hasChildren: "boolean",
type: "string"
});
can.connect.baseMap({
Map: Entity,
url: "/api/entities"
});
var folder = new Entity({
id: "0",
name: "ROOT/",
hasChildren: true,
type: "folder"
});
var FolderVM = can.DefineMap.extend({
folder: Entity,
entitiesPromise: {
value: function() {
return Entity.getList({parentId: this.folder.id});
}
},
isOpen: {type: "boolean", value: false},
toggleOpen: function() {
this.isOpen = !this.isOpen;
}
});
can.Component.extend({
tag:...