* @class elFinder - file manager for web
* @author Dmitry (dio) Levashov
var elFinder = function(elm, opts, bootCallback) {
* Objects array of jQuery.Deferred that calls before elFinder boot up
* Plugin name to check for conflicts with bootstrap etc
conflictChecks = ['button', 'tooltip'],
* Node on which elfinder creating
* Object of events originally registered in this node
prevEvents = $.extend(true, {}, $._data(node.get(0), 'events')),
prevContent = $('<div></div>').append(node.contents()).attr('class', node.attr('class') || '').attr('style', node.attr('style') || ''),
* Instance ID. Required to get/set cookie
id = node.attr('id') || node.attr('id', 'elfauto' + $('.elfinder').length).attr('id'),
namespace = 'elfinder-' + id,
mousedown = 'mousedown.'+namespace,
keydown = 'keydown.'+namespace,
keypress = 'keypress.'+namespace,
keyup = 'keyup.'+namespace,
* Is shortcuts/commands enabled
* Store enabled value before ajax request
* List of build-in events which mapped into methods with same names
events = ['enable', 'disable', 'load', 'open', 'reload', 'select', 'add', 'remove', 'change', 'dblclick', 'getfile', 'lockfiles', 'unlockfiles', 'selectfiles', 'unselectfiles', 'dragstart', 'dragstop', 'search', 'searchend', 'viewchange'],
* Rules to validate data from backend
* Current working directory hash
* Current working directory options default
tmbReqCustomData : false,
* Current working directory options
* Hidden Files/dirs cache
* Files/dirs hash cache of each dirs
* Buffer for copied files
* Copied/cuted files hashes
* Prevent from remove its from cache.
* Required for dispaly correct files names in error messages
* Queue for 'open' requests
* Queue for only cwd requests e.g. `tmb`
base = new self.command(self),
* Number: pixcel or String: Number + "%"
* Base node object or selector
* Element which is the reference of the height percentage
* @default null | $(window) (if height is percentage)
* MIME type list(Associative array) handled as a text file
* elfinder path for sound played on remove
* JSON.stringify of previous fm.sorters
* Map table of file extention to MIME-Type
* Disabled page unload function
diableUnloadCheck = false,
beeper = $(document.createElement('audio')).hide().appendTo('body')[0],
// NOTES: Do not touch data object
var volumeid, contextmenu, emptyDirs = {}, stayDirs = {},
rmClass, hashes, calc, gc, collapsed, prevcwd, sorterStr, diff;
// support volume driver option `uiCmdMap`
self.commandMap = (data.options.uiCmdMap && Object.keys(data.options.uiCmdMap).length)? data.options.uiCmdMap : {};
if (uiCmdMapPrev !== JSON.stringify(self.commandMap)) {
uiCmdMapPrev = JSON.stringify(self.commandMap);
// remove only files from prev cwd
// and collapsed directory (included 100+ directories) to empty for perfomance tune in DnD
rmClass = 'elfinder-subtree-loaded ' + self.res('class', 'navexpand');
collapsed = self.res('class', 'navcollapse');
hashes = Object.keys(files);
var isDir = (files[i].mime === 'directory'),
&& self.navHash2Elm(files[i].hash).is(':hidden')
&& self.navHash2Elm(phash).next('.elfinder-navbar-subtree').children().length > 100
&& (isDir || phash !== cwd)
if (isDir && !emptyDirs[phash]) {
.next('.elfinder-navbar-subtree').empty();
gcJobRes && gcJobRes._abort();
gcJobRes = self.asyncJob(calc, hashes, {
var hd = self.storage('hide') || {items: {}};
if (Object.keys(hiddenFiles).length) {
$.each(hiddenFiles, function(h) {
self.trigger('filesgc').one('filesgc', function() {
self.one('opendone', function() {
if (! node.data('lazycnt')) {
self.one('lazydone', gc);
diff = self.diff([data.cwd], true);
if (diff.changed.length) {
cache(diff.changed, 'change');
self.change({changed: diff.changed});
data.changed && data.changed.length && cache(data.changed, 'change');
// trigger event 'sorterupdate'
sorterStr = JSON.stringify(self.sorters);
if (prevSorterStr !== sorterStr) {
self.trigger('sorterupdate');
prevSorterStr = sorterStr;
* Store info about files/dirs in "files" object.
* @param String data type
cache = function(data, type) {
var type = type || 'files',
keeps = ['sizeInfo', 'encoding'],
defsorter = { name: true, perm: true, date: true, size: true, kind: true },
sorterChk = !self.sorters._checked && (type === 'files'),
setSorter = function(file) {
$.each(self.sortRules, function(key) {
if (defsorter[key] || typeof f[key] !== 'undefined' || (key === 'mode' && typeof f.perm !== 'undefined')) {
self.sorters = self.arrayFlip(sorters, true);
self.sorters._checked = true;
hideData = self.storage('hide') || {},
hides = hideData.items || {},
f, i, i1, keepProp, parents, hidden;
for (i = 0; i < l; i++) {
f = Object.assign({}, data[i]);
hidden = (!hideData.show && hides[f.hash])? true : false;
if (f.name && f.hash && f.mime) {
if (sorterChk && f.phash === cwd) {
if (f.phash && (type === 'add' || (type === 'change' && (!files[f.hash] || f.size !== files[f.hash])))) {
if (parents = self.parents(f.phash)) {
$.each(parents, function() {
changedParents[this] = true;
for (i1 =0; i1 < keeps.length; i1++) {
if(files[f.hash][keeps[i1]] && ! f[keeps[i1]]) {
f[keeps[i1]] = files[f.hash][keeps[i1]];
if (f.sizeInfo && !f.size) {
f.size = f.sizeInfo.size;
deleteCache(files[f.hash], true);
if (f.mime === 'directory' && !ownFiles[f.hash]) {
if (!ownFiles[f.phash]) {
ownFiles[f.phash][f.hash] = true;
$.each(Object.keys(changedParents), function() {
var target = files[this];
if (target && target.sizeInfo) {