File: //home/retile.ru/public_html/admin/view/javascript/d_elfinder/ui/tree.js
"use strict";
/**
* @class elFinder folders tree
*
* @author Dmitry (dio) Levashov
**/
$.fn.elfindertree = function(fm, opts) {
var treeclass = fm.res('class', 'tree');
this.not('.'+treeclass).each(function() {
var c = 'class',
/**
* Root directory class name
*
* @type String
*/
root = fm.res(c, 'treeroot'),
/**
* Open root dir if not opened yet
*
* @type Boolean
*/
openRoot = opts.openRootOnLoad,
/**
* Subtree class name
*
* @type String
*/
subtree = fm.res(c, 'navsubtree'),
/**
* Directory class name
*
* @type String
*/
navdir = fm.res(c, 'treedir'),
/**
* Collapsed arrow class name
*
* @type String
*/
collapsed = fm.res(c, 'navcollapse'),
/**
* Expanded arrow class name
*
* @type String
*/
expanded = fm.res(c, 'navexpand'),
/**
* Class name to mark arrow for directory with already loaded children
*
* @type String
*/
loaded = 'elfinder-subtree-loaded',
/**
* Arraw class name
*
* @type String
*/
arrow = fm.res(c, 'navarrow'),
/**
* Current directory class name
*
* @type String
*/
active = fm.res(c, 'active'),
/**
* Droppable dirs dropover class
*
* @type String
*/
dropover = fm.res(c, 'adroppable'),
/**
* Hover class name
*
* @type String
*/
hover = fm.res(c, 'hover'),
/**
* Disabled dir class name
*
* @type String
*/
disabled = fm.res(c, 'disabled'),
/**
* Draggable dir class name
*
* @type String
*/
draggable = fm.res(c, 'draggable'),
/**
* Droppable dir class name
*
* @type String
*/
droppable = fm.res(c, 'droppable'),
/**
* Droppable options
*
* @type Object
*/
droppableopts = $.extend({}, fm.droppable, {
hoverClass : hover+' '+dropover,
// show subfolders on dropover
over : function() {
var link = $(this);
if (link.is('.'+collapsed+':not(.'+expanded+')')) {
setTimeout(function() {
link.is('.'+dropover) && link.children('.'+arrow).click();
}, 500);
}
}
}),
spinner = $(fm.res('tpl', 'navspinner')),
/**
* Directory html template
*
* @type String
*/
tpl = fm.res('tpl', 'navdir'),
/**
* Permissions marker html template
*
* @type String
*/
ptpl = fm.res('tpl', 'perms'),
/**
* Symlink marker html template
*
* @type String
*/
stpl = fm.res('tpl', 'symlink'),
/**
* Html template replacement methods
*
* @type Object
*/
replace = {
id : function(dir) { return fm.navHash2Id(dir.hash) },
cssclass : function(dir) { return (dir.phash ? '' : root)+' '+navdir+' '+fm.perms2class(dir)+' '+(dir.dirs && !dir.link ? collapsed : ''); },
permissions : function(dir) { return !dir.read || !dir.write ? ptpl : ''; },
symlink : function(dir) { return dir.alias ? stpl : ''; }
},
/**
* Return html for given dir
*
* @param Object directory
* @return String
*/
itemhtml = function(dir) {
dir.name = fm.escape(dir.name);
return tpl.replace(/(?:\{([a-z]+)\})/ig, function(m, key) {
return dir[key] || (replace[key] ? replace[key](dir) : '');
});
},
/**
* Return only dirs from files list
*
* @param Array files list
* @return Array
*/
filter = function(files) {
return $.map(files||[], function(f) { return f.mime == 'directory' ? f : null });
},
/**
* Find parent subtree for required directory
*
* @param String dir hash
* @return jQuery
*/
findSubtree = function(hash) {
return hash ? tree.find('#'+fm.navHash2Id(hash)).next('.'+subtree) : tree;
},
/**
* Find directory (wrapper) in required node
* before which we can insert new directory
*
* @param jQuery parent directory
* @param Object new directory
* @return jQuery
*/
findSibling = function(subtree, dir) {
var node = subtree.children(':first'),
info;
while (node.length) {
if ((info = fm.file(fm.navId2Hash(node.children('[id]').attr('id'))))
&& dir.name.localeCompare(info.name) < 0) {
return node;
}
node = node.next();
}
return $('');
},
/**
* Add new dirs in tree
*
* @param Array dirs list
* @return void
*/
updateTree = function(dirs) {
var length = dirs.length,
orphans = [],
i, dir, html, parent, sibling;
for (i = 0; i < length; i++) {
dir = dirs[i];
if (tree.find('#'+fm.navHash2Id(dir.hash)).length) {
continue;
}
if ((parent = findSubtree(dir.phash)).length) {
html = itemhtml(dir);
if (dir.phash && (sibling = findSibling(parent, dir)).length) {
sibling.before(html);
} else {
parent.append(html);
}
} else {
orphans.push(dir);
}
}
if (orphans.length && orphans.length < length) {
return updateTree(orphans);
}
updateDroppable();
},
/**
* Mark current directory as active
* If current directory is not in tree - load it and its parents
*
* @return void
*/
sync = function() {
var cwd = fm.cwd().hash,
current = tree.find('#'+fm.navHash2Id(cwd)),
rootNode;
if (openRoot) {
rootNode = tree.find('#'+fm.navHash2Id(fm.root()));
rootNode.is('.'+loaded) && rootNode.addClass(expanded).next('.'+subtree).show();
openRoot = false;
}
if (!current.is('.'+active)) {
tree.find('.'+navdir+'.'+active).removeClass(active);
current.addClass(active);
}
if (opts.syncTree) {
if (current.length) {
current.parentsUntil('.'+root).filter('.'+subtree).show().prev('.'+navdir).addClass(expanded);
} else if (fm.newAPI) {
fm.request({
data : {cmd : 'parents', target : cwd},
preventFail : true
})
.done(function(data) {
var dirs = filter(data.tree);
updateTree(dirs);
updateArrows(dirs, loaded)
cwd == fm.cwd().hash && sync();
});
}
}
},
/**
* Make writable and not root dirs droppable
*
* @return void
*/
updateDroppable = function() {
tree.find('.'+navdir+':not(.'+droppable+',.elfinder-ro,.elfinder-na)').droppable(droppableopts);
},
/**
* Check required folders for subfolders and update arrow classes
*
* @param Array folders to check
* @param String css class
* @return void
*/
updateArrows = function(dirs, cls) {
var sel = cls == loaded
? '.'+collapsed+':not(.'+loaded+')'
: ':not(.'+collapsed+')';
//tree.find('.'+subtree+':has(*)').prev(':not(.'+collapsed+')').addClass(collapsed)
$.each(dirs, function(i, dir) {
tree.find('#'+fm.navHash2Id(dir.phash)+sel)
.filter(function() { return $(this).next('.'+subtree).children().length > 0 })
.addClass(cls);
})
},
/**
* Navigation tree
*
* @type JQuery
*/
tree = $(this).addClass(treeclass)
// make dirs draggable and toggle hover class
.delegate('.'+navdir, 'hover', function(e) {
var link = $(this),
enter = e.type == 'mouseenter';
if (!link.is('.'+dropover+' ,.'+disabled)) {
enter && !link.is('.'+root+',.'+draggable+',.elfinder-na,.elfinder-wo') && link.draggable(fm.draggable);
link.toggleClass(hover, enter);
}
})
// add/remove dropover css class
.delegate('.'+navdir, 'dropover dropout drop', function(e) {
$(this)[e.type == 'dropover' ? 'addClass' : 'removeClass'](dropover+' '+hover);
})
// open dir or open subfolders in tree
.delegate('.'+navdir, 'click', function(e) {
var link = $(this),
hash = fm.navId2Hash(link.attr('id')),
file = fm.file(hash);
fm.trigger('searchend');
if (hash != fm.cwd().hash && !link.is('.'+disabled)) {
fm.exec('open', file.thash || hash);
} else if (link.is('.'+collapsed)) {
link.children('.'+arrow).click();
}
})
// toggle subfolders in tree
.delegate('.'+navdir+'.'+collapsed+' .'+arrow, 'click', function(e) {
var arrow = $(this),
link = arrow.parent('.'+navdir),
stree = link.next('.'+subtree);
e.stopPropagation();
if (link.is('.'+loaded)) {
link.toggleClass(expanded);
stree.slideToggle()
} else {
spinner.insertBefore(arrow);
link.removeClass(collapsed);
fm.request({cmd : 'tree', target : fm.navId2Hash(link.attr('id'))})
.done(function(data) {
updateTree(filter(data.tree));
if (stree.children().length) {
link.addClass(collapsed+' '+expanded);
stree.slideDown();
}
sync();
})
.always(function(data) {
spinner.remove();
link.addClass(loaded);
});
}
})
.delegate('.'+navdir, 'contextmenu', function(e) {
e.preventDefault();
fm.trigger('contextmenu', {
'type' : 'navbar',
'targets' : [fm.navId2Hash($(this).attr('id'))],
'x' : e.clientX,
'y' : e.clientY
});
})
;
// move tree into navbar
tree.parent().find('.elfinder-navbar').append(tree).show();
fm.open(function(e) {
var data = e.data,
dirs = filter(data.files);
data.init && tree.empty();
if (dirs.length) {
updateTree(dirs);
updateArrows(dirs, loaded);
}
sync();
})
// add new dirs
.add(function(e) {
var dirs = filter(e.data.added);
if (dirs.length) {
updateTree(dirs);
updateArrows(dirs, collapsed);
}
})
// update changed dirs
.change(function(e) {
var dirs = filter(e.data.changed),
l = dirs.length,
dir, node, tmp, realParent, reqParent, realSibling, reqSibling, isExpanded, isLoaded;
while (l--) {
dir = dirs[l];
if ((node = tree.find('#'+fm.navHash2Id(dir.hash))).length) {
if (dir.phash) {
realParent = node.closest('.'+subtree);
reqParent = findSubtree(dir.phash);
realSibling = node.parent().next();
reqSibling = findSibling(reqParent, dir);
if (!reqParent.length) {
continue;
}
if (reqParent[0] !== realParent[0] || realSibling.get(0) !== reqSibling.get(0)) {
reqSibling.length ? reqSibling.before(node) : reqParent.append(node);
}
}
isExpanded = node.is('.'+expanded);
isLoaded = node.is('.'+loaded);
tmp = $(itemhtml(dir));
node.replaceWith(tmp.children('.'+navdir));
if (dir.dirs
&& (isExpanded || isLoaded)
&& (node = tree.find('#'+fm.navHash2Id(dir.hash)))
&& node.next('.'+subtree).children().length) {
isExpanded && node.addClass(expanded);
isLoaded && node.addClass(loaded);
}
}
}
sync();
updateDroppable();
})
// remove dirs
.remove(function(e) {
var dirs = e.data.removed,
l = dirs.length,
node, stree;
while (l--) {
if ((node = tree.find('#'+fm.navHash2Id(dirs[l]))).length) {
stree = node.closest('.'+subtree);
node.parent().detach();
if (!stree.children().length) {
stree.hide().prev('.'+navdir).removeClass(collapsed+' '+expanded+' '+loaded);
}
}
}
})
// add/remove active class for current dir
.bind('search searchend', function(e) {
tree.find('#'+fm.navHash2Id(fm.cwd().hash))[e.type == 'search' ? 'removeClass' : 'addClass'](active);
})
// lock/unlock dirs while moving
.bind('lockfiles unlockfiles', function(e) {
var lock = e.type == 'lockfiles',
act = lock ? 'disable' : 'enable',
dirs = $.map(e.data.files||[], function(h) {
var dir = fm.file(h);
return dir && dir.mime == 'directory' ? h : null;
})
$.each(dirs, function(i, hash) {
var dir = tree.find('#'+fm.navHash2Id(hash));
if (dir.length) {
dir.is('.'+draggable) && dir.draggable(act);
dir.is('.'+droppable) && dir.droppable(active);
dir[lock ? 'addClass' : 'removeClass'](disabled);
}
});
})
});
return this;
}