CanJS File Navigator (Simple) [CanJS 4]
Finished version of the CanJS File Navigator Guide (Simple)
by donejs
HTML
<script src="https://unpkg.com/can@4/dist/global/can.all.js"></script>
<script type="text/stache" id="entities-template">
<span on:click="toggleOpen()">{{name}}</span>
{{#if(isOpen)}}
<ul>
{{#each(./children)}}
<li class="{{type}} {{#if(hasChildren)}}hasChildren{{/if}}">
{{#eq(type, 'file')}}
π <span>{{name}}</span>
{{else}}
π {{>entities}}
{{/eq}}
</li>
{{/each}}
</ul>
{{/if}}
</script>
<!--
<span>root</span>
<ul>
<li class='file'>π <span>cats.png</span></li>
<li class='file'>π <span>dogs.jpg</span></li>
<li class='folder'>π <span>empty folder</span></li>
<li class='folder hasChildren'>π <span>folder with files</span></li>
<li class='folder hasChildren'>
π <span>folder loading files </span>
<div class="loading">Loading</div>
</li>
<li class='folder hasChildren'>
π <span>opened folder</span>
<ul>
<li class='file'>π <span>file1.png</span></li>
<li class='file'>π <span>file2.jpg</span></li>
</ul>
</li>
</ul>
-->
<script src="https://unpkg.com/can@4/dist/global/can.all.js"></script>
<script type="text/javascript">
let entityId = 1;
const makeEntities = function(parentId, depth){
if (depth > 5) {
return [];
}
const entitiesCount = can.fixture.rand(6);
const entities = [];
for (let i = 0 ; i < entitiesCount; i++) {
// The id for this entity
const id = ""+(entityId++);
// If the entity is a folder or file
const isFolder = Math.random() > 0.3;
// The children for this folder.
const children = isFolder ? makeEntities(id, depth+1) : [];
const entity = {
id: id,
name: (isFolder ? "Folder" : "File")+" "+id,
parentId: parentId,
type: (isFolder ? "folder" : "file"),
hasChildren: children.length ? true : false
};
entities.push(entity);
if (isFolder) {
entity.children = children;
}
}
return...
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
const Entity = can.DefineMap.extend("Entity", {
id: "string",
name: "string",
parentId: "string",
hasChildren: "boolean",
type: "string",
children: [{
type: function(entity) {
return new Entity(entity)
}
}],
isOpen: {type: "boolean", default: false},
toggleOpen: function(){
this.isOpen = !this.isOpen;
}
});
const rootEntity = new Entity(rootEntityData);
const template = can.stache.from("entities-template");
can.stache.registerPartial("entities", template);
const frag = template(rootEntity);
document.body.appendChild( frag );