Clean tree code.

This commit is contained in:
Lars Jung 2016-06-28 19:44:52 +02:00
parent 9c4f3111ba
commit 3b8edf9ad8
2 changed files with 78 additions and 114 deletions

View file

@ -21,10 +21,6 @@
} }
} }
li {
clear: left;
}
.active > a { .active > a {
font-weight: bold; font-weight: bold;
} }
@ -38,23 +34,29 @@
cursor: pointer; cursor: pointer;
img { img {
.eased-transition; // .eased-transition;
width: 20px; width: 20px;
height: 20px; height: 20px;
zoom: 1;
} }
&.open { }
img {
transform: rotate(90deg); .item {
zoom: 1; clear: left;
}
&.open > .indicator img {
transform: rotate(90deg);
} }
&.unknown { &.unknown > .indicator {
opacity: 0.3; opacity: 0.3;
} }
&.none { &.none > .indicator {
opacity: 0; opacity: 0;
cursor: inherit; cursor: inherit;
} }
&.unknown > .content, &.none > .content, &.closed > .content {
display: none;
}
} }
.icon { .icon {
@ -72,8 +74,12 @@
} }
.content { .content {
list-style: none;
margin: 0; margin: 0;
padding: 0 0 0 20px; padding: 0 0 0 20px;
} }
.summary {
color: @col-text-disabled-black;
padding: 0 0 0 8px;
}
} }

View file

@ -14,12 +14,12 @@ const settings = Object.assign({
ignorecase: true ignorecase: true
}, allsettings.tree); }, allsettings.tree);
const itemTpl = const itemTpl =
`<div class="item"> `<div class="item folder">
<span class="indicator none"> <span class="indicator">
<img src="${resource.image('tree-indicator')}"/> <img src="${resource.image('tree-indicator')}"/>
</span> </span>
<a> <a>
<span class="icon"><img/></span> <span class="icon"><img src="${resource.icon('folder')}"/></span>
<span class="label"></span> <span class="label"></span>
</a> </a>
</span>`; </span>`;
@ -33,7 +33,31 @@ const settingsTpl =
const storekey = 'ext/tree'; const storekey = 'ext/tree';
const cmpFn = (item1, item2) => { const closestItem = el => {
while (!el._item && el.parentNode) {
el = el.parentNode;
}
return el._item;
};
const onIndicatorClick = ev => {
const item = closestItem(ev.target);
if (item._treeState === 'unknown') {
item.fetchContent(() => {
item._treeState = 'open';
update(item); // eslint-disable-line no-use-before-define
});
} else if (item._treeState === 'open') {
item._treeState = 'closed';
item._$tree.rmCls('open').addCls('closed');
} else if (item._treeState === 'closed') {
item._treeState = 'open';
item._$tree.rmCls('closed').addCls('open');
}
};
const cmpItems = (item1, item2) => {
let val1 = item1.label; let val1 = item1.label;
let val2 = item2.label; let val2 = item2.label;
@ -46,120 +70,54 @@ const cmpFn = (item1, item2) => {
}; };
const update = item => { const update = item => {
const subfolders = item.getSubfolders();
const subLen = subfolders.length;
const subMax = settings.maxSubfolders;
const $html = dom(itemTpl); const $html = dom(itemTpl);
const $indicator = $html.find('.indicator');
const $a = $html.find('a');
const $img = $html.find('.icon img');
const $label = $html.find('.label');
$html.addCls(item.isFolder() ? 'folder' : 'file'); $html.find('.indicator').on('click', onIndicatorClick);
$indicator.on('click', createOnIndicatorClick()); // eslint-disable-line no-use-before-define $html.find('.label').text(item.label);
location.setLink($html.find('a'), item);
location.setLink($a, item); if (item.isCurrentFolder()) {
$img.attr('src', resource.icon('folder')); $html.addCls('active');
$label.text(item.label); }
if (item.isFolder()) { if (!item.isManaged) {
const subfolders = item.getSubfolders(); $html.find('.icon img').attr('src', resource.icon('folder-page'));
}
// indicator // indicator
if (item.isManaged && !item.isContentFetched || subfolders.length) { item._treeState = item._treeState || 'none';
$indicator.rmCls('none'); if (item.isManaged && !item.isContentFetched) {
item._treeState = 'unknown';
} else if (!subLen) {
item._treeState = 'none';
}
$html.addCls(item._treeState);
if (item.isManaged && !item.isContentFetched) { // subfolders
$indicator.addCls('unknown'); if (subLen) {
} else if (item.isContentVisible) { const $ul = dom('<div class="content"></div>').appTo($html);
$indicator.addCls('open'); subfolders.sort(cmpItems);
} else { each(subfolders.slice(0, subMax), e => $ul.app(update(e)));
$indicator.addCls('close'); if (subLen > subMax) {
} $ul.app(`<div class="summary">… ${subLen - subMax} more subfolders</div>`);
}
// is it the current folder?
if (item.isCurrentFolder()) {
$html.addCls('active');
}
// does it have subfolders?
if (subfolders.length) {
subfolders.sort(cmpFn);
const $ul = dom('<ul class="content"></ul>').appTo($html);
let counter = 0;
each(subfolders, e => {
counter += 1;
if (counter <= settings.maxSubfolders) {
dom('<li></li>').app(update(e)).appTo($ul);
}
});
if (subfolders.length > settings.maxSubfolders) {
dom('<li class="summary">… ' + (subfolders.length - settings.maxSubfolders) + ' more subfolders</li>').appTo($ul);
}
if (!item.isContentVisible) {
$ul.hide();
}
}
// reflect folder status
if (!item.isManaged) {
$img.attr('src', resource.icon('folder-page'));
} }
} }
if (item.$tree) { if (item._$tree) {
item.$tree.rpl($html); item._$tree.rpl($html);
} }
item.$tree = $html; item._$tree = $html;
$html[0]._item = item; $html[0]._item = item;
return $html; return $html;
}; };
const closestItem = el => {
while (!el._item && el.parentNode) {
el = el.parentNode;
}
return el._item;
};
const createOnIndicatorClick = () => {
const slide = ($indicator, $content, down) => {
const item = closestItem($indicator[0]);
item.isContentVisible = down;
$indicator.rmCls('open').rmCls('close').addCls(down ? 'open' : 'close');
// $content[down ? 'slideDown' : 'slideUp']();
$content[down ? 'show' : 'hide']();
};
return ev => {
const item = closestItem(ev.target);
let $item = item.$tree;
let $indicator = dom($item.find('.indicator')[0]);
let $content = dom($item.find('ul.content')[0]);
if ($indicator.hasCls('unknown')) {
item.fetchContent(() => {
item.isContentVisible = false;
$item = update(item);
$indicator = dom($item.find('.indicator')[0]);
$content = dom($item.find('ul.content')[0]);
if (!$indicator.hasCls('none')) {
slide($indicator, $content, true);
}
});
} else if ($indicator.hasCls('open')) {
slide($indicator, $content, false);
} else if ($indicator.hasCls('close')) {
slide($indicator, $content, true);
}
};
};
const fetchTree = (item, callback) => { const fetchTree = (item, callback) => {
item.isContentVisible = true; item._treeState = 'open';
item.fetchContent(() => { item.fetchContent(() => {
if (item.parent) { if (item.parent) {
fetchTree(item.parent, callback); fetchTree(item.parent, callback);