: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @class elFinder folders tree
* @author Dmitry (dio) Levashov
jQuery.fn.elfindertree = function(fm, opts) {
var treeclass = fm.res('class', 'tree');
this.not('.'+treeclass).each(function() {
var c = 'class', mobile = fm.UA.Mobile,
* Root directory class name
root = fm.res(c, 'treeroot'),
* Open root dir if not opened yet
openRoot = opts.openRootOnLoad,
* Open current work dir if not opened yet
openCwd = opts.openCwdOnOpen,
* Auto loading current directory parents and do expand their node
syncTree = openCwd || opts.syncTree,
subtree = fm.res(c, 'navsubtree'),
navdir = fm.res(c, 'treedir'),
selNavdir = 'span.' + navdir,
* Collapsed arrow class name
collapsed = fm.res(c, 'navcollapse'),
* Expanded arrow class name
expanded = fm.res(c, 'navexpand'),
* Class name to mark arrow for directory with already loaded children
loaded = 'elfinder-subtree-loaded',
* Class name to mark need subdirs request
chksubdir = 'elfinder-subtree-chksubdir',
arrow = fm.res(c, 'navarrow'),
* Current directory class name
active = fm.res(c, 'active'),
* Droppable dirs dropover class
dropover = fm.res(c, 'adroppable'),
hover = fm.res(c, 'hover'),
* Disabled dir class name
disabled = fm.res(c, 'disabled'),
* Draggable dir class name
draggable = fm.res(c, 'draggable'),
* Droppable dir class name
droppable = fm.res(c, 'droppable'),
wrapperRoot = 'elfinder-navbar-wrapper-root',
* Un-disabled cmd `paste` volume's root wrapper class
pastable = 'elfinder-navbar-wrapper-pastable',
* Un-disabled cmd `upload` volume's root wrapper class
uploadable = 'elfinder-navbar-wrapper-uploadable',
* Is position x inside Navbar
insideNavbar = function(x) {
var left = navbar.offset().left;
return left <= x && x <= left + navbar.width();
* To call subdirs elements queue
* To exec subdirs elements ids
* Request subdirs to backend
subdirs = function(ids) {
jQuery.each(ids, function(i, id) {
subdirsQue[id] && targets.push(fm.navId2Hash(id));
if (res && res.subdirs) {
jQuery.each(res.subdirs, function(hash, subdirs) {
var elm = fm.navHash2Elm(hash);
elm.removeClass(chksubdir);
elm[subdirs? 'addClass' : 'removeClass'](collapsed);
* To check target element is in window of subdirs
checkSubdirs = function() {
var ids = Object.keys(subdirsQue);
subdirsJobRes && subdirsJobRes._abort();
execSubdirsTm && clearTimeout(execSubdirsTm);
subdirsJobRes = fm.asyncJob(function(id) {
return fm.isInWindow(jQuery('#'+id))? id : null;
}, ids, { numPerOnce: 200 })
* Exec subdirs as batch request
execSubdirs = function() {
var cnt = opts.subdirsMaxConn - subdirsPending,
atOnce = fm.maxTargets? Math.min(fm.maxTargets, opts.subdirsAtOnce) : opts.subdirsAtOnce,
execSubdirsTm && cancelAnimationFrame(execSubdirsTm);
if (subdirsExecQue.length) {
for (i = 0; i < cnt; i++) {
if (subdirsExecQue.length) {
subdirs(subdirsExecQue.splice(0, atOnce)).always(function() {
execSubdirsTm = requestAnimationFrame(function() {
subdirsExecQue.length && execSubdirs();
drop = fm.droppable.drop,
droppableopts = jQuery.extend(true, {}, fm.droppable, {
// show subfolders on dropover
helper.data('dropover', helper.data('dropover') + 1);
dst.data('dropover', true);
if (ui.helper.data('namespace') !== fm.namespace || ! fm.insideWorkzone(e.pageX, e.pageY)) {
helper.removeClass('elfinder-drag-helper-move elfinder-drag-helper-plus');
if (! insideNavbar(e.clientX)) {
helper.removeClass('elfinder-drag-helper-move elfinder-drag-helper-plus');
if (dst.is('.'+collapsed+':not(.'+expanded+')')) {
dst.data('expandTimer', setTimeout(function() {
dst.is('.'+collapsed+'.'+hover) && dst.children('.'+arrow).trigger('click');
if (dst.is('.elfinder-ro,.elfinder-na')) {
dst.removeClass(dropover);
//helper.removeClass('elfinder-drag-helper-move elfinder-drag-helper-plus');
hash = fm.navId2Hash(dst.attr('id'));
dst.data('dropover', hash);
jQuery.each(ui.helper.data('files'), function(i, h) {
if (h === hash || (fm.file(h).phash === hash && !ui.helper.hasClass('elfinder-drag-helper-plus'))) {
return false; // break jQuery.each
if (helper.data('locked')) {
status = 'elfinder-drag-helper-plus';
status = 'elfinder-drag-helper-move';
if (fm._commands.copy && (e.shiftKey || e.ctrlKey || e.metaKey)) {
status += ' elfinder-drag-helper-plus';
dst.hasClass(dropover) && helper.addClass(status);
requestAnimationFrame(function(){ dst.hasClass(dropover) && helper.addClass(status); });
if (insideNavbar(e.clientX)) {
helper.removeClass('elfinder-drag-helper-move elfinder-drag-helper-plus');
helper.data('dropover', Math.max(helper.data('dropover') - 1, 0));
dst.data('expandTimer') && clearTimeout(dst.data('expandTimer'));
dst.removeData('dropover')
.removeClass(hover+' '+dropover);
deactivate : function() {
jQuery(this).removeData('dropover')
.removeClass(hover+' '+dropover);
insideNavbar(e.clientX) && drop.call(this, e, ui);
spinner = jQuery(fm.res('tpl', 'navspinner')),
* Directory html template
tpl = fm.res('tpl', 'navdir'),
* Permissions marker html template
ptpl = fm.res('tpl', 'perms'),
* Lock marker html template
ltpl = fm.res('tpl', 'lock'),
* Symlink marker html template
stpl = fm.res('tpl', 'symlink'),
* Directory hashes that has more pages
* Html template replacement methods
id : function(dir) { return fm.navHash2Id(dir.hash); },
name : function(dir) { return fm.escape(dir.i18 || dir.name); },
cssclass : function(dir) {
var cname = (dir.phash && ! dir.isroot ? '' : root)+' '+navdir+' '+fm.perms2class(dir);
dir.dirs && !dir.link && (cname += ' ' + collapsed) && dir.dirs == -1 && (cname += ' ' + chksubdir);
opts.getClass && (cname += ' ' + opts.getClass(dir));
dir.csscls && (cname += ' ' + fm.escape(dir.csscls));
title : function(dir) { return opts.attrTitle? (' title="' + fm.escape(fm.path(dir.hash, true) || dir.i18 || dir.name) + '"') : ''; },
if (!dir.phash || dir.isroot) {
if (!dir.disabled || dir.disabled.length < 1) {
cls += ' '+pastable+' '+uploadable;
if (jQuery.inArray('paste', dir.disabled) === -1) {
if (jQuery.inArray('upload', dir.disabled) === -1) {
permissions : function(dir) { return !dir.read || !dir.write ? ptpl : ''; },
symlink : function(dir) { return dir.alias ? stpl : ''; },
style : function(dir) { return dir.icon ? fm.getIconStyle(dir) : ''; }
* Return html for given dir
* @param Object directory
itemhtml = function(dir) {
return tpl.replace(/(?:\{([a-z]+)\})/ig, function(m, key) {
var res = replace[key] ? replace[key](dir) : (dir[key] || '');
if (key === 'id' && dir.dirs == -1) {
* Return only dirs from files list
* @param Array files list
* @param Boolean do check exists
filter = function(files, checkExists) {
return jQuery.map(files || [], function(f) {
return (f.mime === 'directory' && (!checkExists || fm.navHash2Elm(f.hash).length)) ? f : null;
* Find parent subtree for required directory
findSubtree = function(hash) {
return hash ? fm.navHash2Elm(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
findSibling = function(subtree, dir) {
var node = subtree.children(':first'),
info = fm.file(fm.navId2Hash(node.children('[id]').attr('id')));
if ((info = fm.file(fm.navId2Hash(node.children('[id]').attr('id'))))
&& compare(dir, info) < 0) {
return subtree.children('button.elfinder-navbar-pager-next');