docs: function(elm, opts, bootCallback) {
  		//this.time('load');
  	var self = this,
  		
  		/**
  		 * Objects array of jQuery.Deferred that calls before elFinder boot up
  		 * 
  		 * @type Array
  		 */
  		dfrdsBeforeBootup = [],
  		
  		/**
  		 * Plugin name to check for conflicts with bootstrap etc
  		 *
  		 * @type Array
  		 **/
  		conflictChecks = ['button'],
  		
  		/**
  		 * Node on which elfinder creating
  		 *
  		 * @type jQuery
  		 **/
  		node = $(elm),
  		
  		/**
  		 * Object of events originally registered in this node
  		 * 
  		 * @type Object
  		 */
  		prevEvents = $.extend(true, {}, $._data(node.get(0), 'events')),
  		
  		/**
  		 * Store node contents.
  		 *
  		 * @see this.destroy
  		 * @type jQuery
  		 **/
  		prevContent = $('<div/>').append(node.contents()).attr('class', node.attr('class') || '').attr('style', node.attr('style') || ''),
  		
  		/**
  		 * Instance ID. Required to get/set cookie
  		 *
  		 * @type String
  		 **/
  		id = node.attr('id') || '',
  		
  		/**
  		 * Events namespace
  		 *
  		 * @type String
  		 **/
  		namespace = 'elfinder-' + (id ? id : Math.random().toString().substr(2, 7)),
  		
  		/**
  		 * Mousedown event
  		 *
  		 * @type String
  		 **/
  		mousedown = 'mousedown.'+namespace,
  		
  		/**
  		 * Keydown event
  		 *
  		 * @type String
  		 **/
  		keydown = 'keydown.'+namespace,
  		
  		/**
  		 * Keypress event
  		 *
  		 * @type String
  		 **/
  		keypress = 'keypress.'+namespace,
  		
  		/**
  		 * Keypup event
  		 *
  		 * @type String
  		 **/
  		keyup    = 'keyup.'+namespace,
  
  		/**
  		 * Is shortcuts/commands enabled
  		 *
  		 * @type Boolean
  		 **/
  		enabled = false,
  		
  		/**
  		 * Store enabled value before ajax request
  		 *
  		 * @type Boolean
  		 **/
  		prevEnabled = false,
  		
  		/**
  		 * List of build-in events which mapped into methods with same names
  		 *
  		 * @type Array
  		 **/
  		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
  		 *
  		 * @type Object
  		 **/
  		rules = {},
  		
  		/**
  		 * Current working directory hash
  		 *
  		 * @type String
  		 **/
  		cwd = '',
  		
  		/**
  		 * Current working directory options default
  		 *
  		 * @type Object
  		 **/
  		cwdOptionsDefault = {
  			path          : '',
  			url           : '',
  			tmbUrl        : '',
  			disabled      : [],
  			separator     : '/',
  			archives      : [],
  			extract       : [],
  			copyOverwrite : true,
  			uploadOverwrite : true,
  			uploadMaxSize : 0,
  			jpgQuality    : 100,
  			tmbCrop       : false,
  			tmb           : false // old API
  		},
  		
  		/**
  		 * Current working directory options
  		 *
  		 * @type Object
  		 **/
  		cwdOptions = {},
  		
  		/**
  		 * Files/dirs cache
  		 *
  		 * @type Object
  		 **/
  		files = {},
  		
  		/**
  		 * Files/dirs hash cache of each dirs
  		 *
  		 * @type Object
  		 **/
  		ownFiles = {},
  		
  		/**
  		 * Selected files hashes
  		 *
  		 * @type Array
  		 **/
  		selected = [],
  		
  		/**
  		 * Events listeners
  		 *
  		 * @type Object
  		 **/
  		listeners = {},
  		
  		/**
  		 * Shortcuts
  		 *
  		 * @type Object
  		 **/
  		shortcuts = {},
  		
  		/**
  		 * Buffer for copied files
  		 *
  		 * @type Array
  		 **/
  		clipboard = [],
  		
  		/**
  		 * Copied/cuted files hashes
  		 * Prevent from remove its from cache.
  		 * Required for dispaly correct files names in error messages
  		 *
  		 * @type Object
  		 **/
  		remember = {},
  		
  		/**
  		 * Queue for 'open' requests
  		 *
  		 * @type Array
  		 **/
  		queue = [],
  		
  		/**
  		 * Queue for only cwd requests e.g. `tmb`
  		 *
  		 * @type Array
  		 **/
  		cwdQueue = [],
  		
  		/**
  		 * Commands prototype
  		 *
  		 * @type Object
  		 **/
  		base = new self.command(self),
  		
  		/**
  		 * elFinder node width
  		 *
  		 * @type String
  		 * @default "auto"
  		 **/
  		width  = 'auto',
  		
  		/**
  		 * elFinder node height
  		 * Number: pixcel or String: Number + "%"
  		 *
  		 * @type Number | String
  		 * @default 400
  		 **/
  		height = 400,
  		
  		/**
  		 * Base node object or selector
  		 * Element which is the reference of the height percentage
  		 *
  		 * @type Object|String
  		 * @default null | $(window) (if height is percentage)
  		 **/
  		heightBase = null,
  		
  		/**
  		 * MIME type list(Associative array) handled as a text file
  		 * 
  		 * @type Object|null
  		 */
  		textMimes = null,
  		
  		/**
  		 * elfinder path for sound played on remove
  		 * @type String
  		 * @default ./sounds/
  		 **/
  		soundPath = 'sounds/',
  		
  		/**
  		 * JSON.stringify of previous fm.sorters
  		 * @type String
  		 */
  		prevSorterStr = '',
  
  		beeper = $(document.createElement('audio')).hide().appendTo('body')[0],
  			
  		syncInterval,
  		autoSyncStop = 0,
  		
  		uiCmdMapPrev = '',
  		
  		gcJobRes = null,
  		
  		open = function(data) {
  			// NOTES: Do not touch data object
  		
  			var volumeid, contextmenu, emptyDirs = {}, stayDirs = {},
  				rmClass, hashes, calc, gc, collapsed, prevcwd, sorterStr;
  			
  			if (self.api >= 2.1) {
  				// 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);
  				}
  			} else {
  				self.options.sync = 0;
  			}
  			
  			if (data.init) {
  				// init - reset cache
  				files = {};
  				ownFiles = {};
  			} else {
  				// remove only files from prev cwd
  				// and collapsed directory (included 100+ directories) to empty for perfomance tune in DnD
  				prevcwd = cwd;
  				rmClass = 'elfinder-subtree-loaded ' + self.res('class', 'navexpand');
  				collapsed = self.res('class', 'navcollapse');
  				hashes = Object.keys(files);
  				calc = function(i) {
  					if (!files[i]) {
  						return true;
  					}
  					
  					var isDir = (files[i].mime === 'directory'),
  						phash = files[i].phash,
  						pnav;
  					
  					if (
  						(!isDir
  							|| emptyDirs[phash]
  							|| (!stayDirs[phash]
  								&& $('#'+self.navHash2Id(files[i].hash)).is(':hidden')
  								&& $('#'+self.navHash2Id(phash)).next('.elfinder-navbar-subtree').children().length > 100
  							)
  						)
  						&& (isDir || phash !== cwd)
  						&& ! remember[i]
  					) {
  						if (isDir && !emptyDirs[phash]) {
  							emptyDirs[phash] = true;
  							$('#'+self.navHash2Id(phash))
  							 .removeClass(rmClass)
  							 .next('.elfinder-navbar-subtree').empty();
  						}
  						deleteCache(files[i]);
  					} else if (isDir) {
  						stayDirs[phash] = true;
  					}
  				};
  				gc = function() {
  					if (hashes.length) {
  						gcJobRes && gcJobRes._abort();
  						gcJobRes = self.asyncJob(calc, hashes, {
  							interval : 20,
  							numPerOnce : 100
  						});
  					}
  				};
  				
  				self.trigger('filesgc').one('filesgc', function() {
  					hashes = [];
  				});
  				
  				self.one('opendone', function() {
  					if (prevcwd !== cwd) {
  						if (! node.data('lazycnt')) {
  							gc();
  						} else {
  							self.one('lazydone', gc);
  						}
  					}
  				});
  			}
  
  			self.sorters = {};
  			cwd = data.cwd.hash;
  			cache(data.files);
  			if (!files[cwd]) {
  				cache([data.cwd]);
  			}
  
  			// trigger event 'sorterupdate'
  			sorterStr = JSON.stringify(self.sorters);
  			if (prevSorterStr !== sorterStr) {
  				self.trigger('sorterupdate');
  				prevSorterStr = sorterStr;
  			}
  
  			self.lastDir(cwd);
  			
  			self.autoSync();
  		},
  		
  		/**
  		 * Store info about files/dirs in "files" object.
  		 *
  		 * @param  Array  files
  		 * @param  String data type
  		 * @return void
  		 **/
  		cache = function(data, type) {
  			var defsorter = { name: true, perm: true, date: true,  size: true, kind: true },
  				sorterChk = !self.sorters._checked,
  				l         = data.length,
  				setSorter = function(file) {
  					var f = file || {},
  						sorters = [];
  					$.each(self.sortRules, function(key) {
  						if (defsorter[key] || typeof f[key] !== 'undefined' || (key === 'mode' && typeof f.perm !== 'undefined')) {
  							sorters.push(key);
  						}
  					});
  					self.sorters = self.arrayFlip(sorters, true);
  					self.sorters._checked = true;
  				},
  				keeps = ['sizeInfo'],
  				changedParents = {},
  				f, i, keepProp, parents;
  
  			for (i = 0; i < l; i++) {
  				f = Object.assign({}, data[i]);
  				if (f.name && f.hash && f.mime) {
  					if (sorterChk && f.phash === cwd) {
  						setSorter(f);
  						sorterChk = false;
  					}
  					
  					if (f.phash && (type === 'add' || type === 'change')) {
  						if (parents = self.parents(f.phash)) {
  							$.each(parents, function() {
  								changedParents[this] = true;
  							});
  						}
  					}
  					
  					if (files[f.hash]) {
  						$.each(keeps, function() {
  							if(files[f.hash][this] && ! f[this]) {
  								f[this] = files[f.hash][this];
  							}
  						});
  						if (f.sizeInfo && !f.size) {
  							f.size = f.sizeInfo.size;
  						}
  						deleteCache(files[f.hash], true);
  					}
  					files[f.hash] = f;
  					if (f.mime === 'directory' && !ownFiles[f.hash]) {
  						ownFiles[f.hash] = {};
  					}
  					if (f.phash) {
  						if (!ownFiles[f.phash]) {
  							ownFiles[f.phash] = {};
  						}
  						ownFiles[f.phash][f.hash] = true;
  					}
  				} 
  			}
  			// delete sizeInfo cache
  			$.each(Object.keys(changedParents), function() {
  				var target = files[this];
  				if (target && target.sizeInfo) {
  					delete target.sizeInfo;
  				}
  			});
  			
  			// for empty folder
  			sorterChk && setSorter();
  		},
  		
  		/**
  		 * Delete file object from files caches
  		 * 
  		 * @param  Array  removed hashes
  		 * @return void
  		 */
  		remove = function(removed) {
  			var l       = removed.length,
  				roots   = {},
  				rm      = function(hash) {
  					var file = files[hash], i;
  					if (file) {
  						if (file.mime === 'directory') {
  							if (roots[hash]) {
  								delete self.roots[roots[hash]];
  							}
  							// restore stats of deleted root parent directory
  							$.each(self.leafRoots, function(phash, roots) {
  								var idx, pdir;
  								if ((idx = $.inArray(hash, roots))!== -1) {
  									if (roots.length === 1) {
  										if ((pdir = Object.assign({}, files[phash])) && pdir._realStats) {
  											$.each(pdir._realStats, function(k, v) {
  												pdir[k] = v;
  											});
  											remove(files[phash]._realStats);
  											self.change({ changed: [pdir] });
  										}
  										delete self.leafRoots[phash];
  									} else {
  										self.leafRoots[phash].splice(idx, 1);
  									}
  								}
  							});
  							if (self.searchStatus.state < 2) {
  								$.each(files, function(h, f) {
  									f.phash == hash && rm(h);
  								});
  							}
  						}
  						if (file.phash) {
  							if (parents = self.parents(file.phash)) {
  								$.each(parents, function() {
  									changedParents[this] = true;
  								});
  							}
  						}
  						deleteCache(files[hash]);
  					}
  				},
  				changedParents = {},
  				parents;
  		
  			$.each(self.roots, function(k, v) {
  				roots[v] = k;
  			});
  			while (l--) {
  				rm(removed[l]);
  			}
  			// delete sizeInfo cache
  			$.each(Object.keys(changedParents), function() {
  				var target = files[this];
  				if (target && target.sizeInfo) {
  					delete target.sizeInfo;
  				}
  			});
  		},
  		
  		/**
  		 * Update file object in files caches
  		 * 
  		 * @param  Array  changed file objects
  		 * @return void
  		 */
  		change = function(changed) {
  			$.each(changed, function(i, file) {
  				var hash = file.hash;
  				if (files[hash]) {
  					$.each(Object.keys(files[hash]), function(i, v){
  						if (typeof file[v] === 'undefined') {
  							delete files[hash][v];
  						}
  					});
  				}
  				files[hash] = files[hash] ? Object.assign(files[hash], file) : file;
  			});
  		},
  		
  		/**
  		 * Delete cache data of files, ownFiles and self.optionsByHashes
  		 * 
  		 * @param  Object  file
  		 * @param  Boolean update
  		 * @return void
  		 */
  		deleteCache = function(file, update) {
  			var hash = file.hash,
  				phash = file.phash;
  			
  			if (phash && ownFiles[phash]) {
  				 delete ownFiles[phash][hash];
  			}
  			if (!update) {
  				ownFiles[hash] && delete ownFiles[hash];
  				self.optionsByHashes[hash] && delete self.optionsByHashes[hash];
  			}
  			delete files[hash];
  		},
  		
  		/**
  		 * Maximum number of concurrent connections on request
  		 * 
  		 * @type Number
  		 */
  		requestMaxConn,
  		
  		/**
  		 * Current number of connections
  		 * 
  		 * @type Number
  		 */
  		requestCnt = 0,
  		
  		/**
  		 * Queue waiting for connection
  		 * 
  		 * @type Array
  		 */
  		requestQueue = [],
  		
  		/**
  		 * Flag to cancel the `open` command waiting for connection
  		 * 
  		 * @type Boolean
  		 */
  		requestQueueSkipOpen = false,
  		
  		/**
  		 * Exec shortcut
  		 *
  		 * @param  jQuery.Event  keydown/keypress event
  		 * @return void
  		 */
  		execShortcut = function(e) {
  			var code    = e.keyCode,
  				ctrlKey = !!(e.ctrlKey || e.metaKey),
  				isMousedown = e.type === 'mousedown',
  				ddm;
  
  			!isMousedown && (self.keyState.keyCode = code);
  			self.keyState.ctrlKey  = ctrlKey;
  			self.keyState.shiftKey = e.shiftKey;
  			self.keyState.metaKey  = e.metaKey;
  			self.keyState.altKey   = e.altKey;
  			if (isMousedown) {
  				return;
  			} else if (e.type === 'keyup') {
  				self.keyState.keyCode = null;
  				return;
  			}
  
  			if (enabled) {
  
  				$.each(shortcuts, function(i, shortcut) {
  					if (shortcut.type    == e.type 
  					&& shortcut.keyCode  == code 
  					&& shortcut.shiftKey == e.shiftKey 
  					&& shortcut.ctrlKey  == ctrlKey 
  					&& shortcut.altKey   == e.altKey) {
  						e.preventDefault();
  						e.stopPropagation();
  						shortcut.callback(e, self);
  						self.debug('shortcut-exec', i+' : '+shortcut.description);
  					}
  				});
  				
  				// prevent tab out of elfinder
  				if (code == $.ui.keyCode.TAB && !$(e.target).is(':input')) {
  					e.preventDefault();
  				}
  				
  				// cancel any actions by [Esc] key
  				if (e.type === 'keydown' && code == $.ui.keyCode.ESCAPE) {
  					// copy or cut 
  					if (! node.find('.ui-widget:visible').length) {
  						self.clipboard().length && self.clipboard([]);
  					}
  					// dragging
  					if ($.ui.ddmanager) {
  						ddm = $.ui.ddmanager.current;
  						ddm && ddm.helper && ddm.cancel();
  					}
  					// button menus
  					self.toHide(node.find('.ui-widget.elfinder-button-menu.elfinder-frontmost:visible'));
  					// trigger keydownEsc
  					self.trigger('keydownEsc', e);
  				}
  
  			}
  		},
  		date = new Date(),
  		utc,
  		i18n,
  		inFrame = (window.parent !== window),
  		parentIframe = (function() {
  			var pifm, ifms;
  			if (inFrame) {
  				try {
  					ifms = $('iframe', window.parent.document);
  					if (ifms.length) {
  						$.each(ifms, function(i, ifm) {
  							if (ifm.contentWindow === window) {
  								pifm = $(ifm);
  								return false;
  							}
  						});
  					}
  				} catch(e) {}
  			}
  			return pifm;
  		})(),
  		/**
  		 * elFinder boot up function
  		 * 
  		 * @type Function
  		 */
  		bootUp,
  		/**
  		 * Original function of XMLHttpRequest.prototype.send
  		 * 
  		 * @type Function
  		 */
  		savedXhrSend;
  	
  	// opts must be an object
  	if (!opts) {
  		opts = {};
  	}
  	
  	// set UA.Angle, UA.Rotated for mobile devices
  	if (self.UA.Mobile) {
  		$(window).on('orientationchange.'+namespace, function() {
  			var a = ((screen && screen.orientation && screen.orientation.angle) || window.orientation || 0) + 0;
  			if (a === -90) {
  				a = 270;
  			}
  			self.UA.Angle = a;
  			self.UA.Rotated = a % 180 === 0? false : true;
  		}).trigger('orientationchange.'+namespace);
  	}
  	
  	// check opt.bootCallback
  	if (opts.bootCallback && typeof opts.bootCallback === 'function') {
  		(function() {
  			var func = bootCallback,
  				opFunc = opts.bootCallback;
  			bootCallback = function(fm, extraObj) {
  				func && typeof func === 'function' && func.call(this, fm, extraObj);
  				opFunc.call(this, fm, extraObj);
  			};
  		})();
  	}
  	delete opts.bootCallback;
  
  	/**
  	 * Protocol version
  	 *
  	 * @type String
  	 **/
  	this.api = null;
  	
  	/**
  	 * elFinder use new api
  	 *
  	 * @type Boolean
  	 **/
  	this.newAPI = false;
  	
  	/**
  	 * elFinder use old api
  	 *
  	 * @type Boolean
  	 **/
  	this.oldAPI = false;
  	
  	/**
  	 * Net drivers names
  	 *
  	 * @type Array
  	 **/
  	this.netDrivers = [];
  	
  	/**
  	 * Base URL of elfFinder library starting from Manager HTML
  	 * 
  	 * @type String
  	 */
  	this.baseUrl = '';
  	
  	/**
  	 * Is elFinder CSS loaded
  	 * 
  	 * @type Boolean
  	 */
  	this.cssloaded = false;
  	
  	/**
  	 * Callback function at boot up that option specified at elFinder starting
  	 * 
  	 * @type Function
  	 */
  	this.bootCallback;
  	
  	/**
  	 * Configuration options
  	 *
  	 * @type Object
  	 **/
  	//this.options = $.extend(true, {}, this._options, opts);
  	this.options = Object.assign({}, this._options);
  	
  	// for old type configuration
  	if (opts.uiOptions) {
  		if (opts.uiOptions.toolbar && Array.isArray(opts.uiOptions.toolbar)) {
  			if ($.isPlainObject(opts.uiOptions.toolbar[opts.uiOptions.toolbar.length - 1])) {
  				self.options.uiOptions.toolbarExtra = Object.assign(self.options.uiOptions.toolbarExtra || {}, opts.uiOptions.toolbar.pop());
  			}
  		}
  	}
  	
  	// Overwrite if opts value is an array
  	(function() {
  		var arrOv = function(obj, base) {
  			if ($.isPlainObject(obj)) {
  				$.each(obj, function(k, v) {
  					if ($.isPlainObject(v)) {
  						if (!base[k]) {
  							base[k] = {};
  						}
  						arrOv(v, base[k]);
  					} else {
  						base[k] = v;
  					}
  				});
  			}
  		};
  		arrOv(opts, self.options);
  	})();
  	
  	// join toolbarExtra to toolbar
  	this.options.uiOptions.toolbar.push(this.options.uiOptions.toolbarExtra);
  	delete this.options.uiOptions.toolbarExtra;
  	
  	// set fm.baseUrl
  	this.baseUrl = (function() {
  		var myTag, myCss, base, baseUrl;
  		
  		if (self.options.baseUrl) {
  			return self.options.baseUrl;
  		} else {
  			baseUrl = '';
  			myTag = $('head > script[src$="js/elfinder.min.js"],script[src$="js/elfinder.full.js"]:first');
  			if (myTag.length) {
  				myCss = $('head > link[href$="css/elfinder.min.css"],link[href$="css/elfinder.full.css"]:first').length;
  				if (! myCss) {
  					// to request CSS auto loading
  					self.cssloaded = null;
  				}
  				baseUrl = myTag.attr('src').replace(/js\/[^\/]+$/, '');
  				if (! baseUrl.match(/^(https?\/\/|\/)/)) {
  					// check <base> tag
  					if (base = $('head > base[href]').attr('href')) {
  						baseUrl = base.replace(/\/$/, '') + '/' + baseUrl; 
  					}
  				}
  			}
  			if (baseUrl !== '') {
  				self.options.baseUrl = baseUrl;
  			} else {
  				if (! self.options.baseUrl) {
  					self.options.baseUrl = './';
  				}
  				baseUrl = self.options.baseUrl;
  			}
  			return baseUrl;
  		}
  	})();
  	
  	// set dispInlineRegex
  	cwdOptionsDefault['dispInlineRegex'] = this.options.dispInlineRegex;
  	
  	// auto load required CSS
  	if (this.options.cssAutoLoad) {
  		(function() {
  			var baseUrl = self.baseUrl;
  			
  			if (self.cssloaded === null) {
  				// hide elFinder node while css loading
  				node.data('cssautoloadHide', $('<style>.elfinder{visibility:hidden;overflow:hidden}</style>'));
  				$('head').append(node.data('cssautoloadHide'));
  				
  				// load CSS
  				self.loadCss([baseUrl+'css/elfinder.min.css', baseUrl+'css/theme.css']);
  				
  				// additional CSS files
  				if (Array.isArray(self.options.cssAutoLoad)) {
  					self.loadCss(self.options.cssAutoLoad);
  				}
  			}
  			self.options.cssAutoLoad = false;
  		})();
  	}
  	
  	/**
  	 * Volume option to set the properties of the root Stat
  	 * 
  	 * @type Object
  	 */
  	this.optionProperties = {
  		icon: void(0),
  		csscls: void(0),
  		tmbUrl: void(0),
  		uiCmdMap: {},
  		netkey: void(0),
  		disabled: []
  	};
  	
  	if (! inFrame && ! this.options.enableAlways && $('body').children().length === 2) { // only node and beeper
  		this.options.enableAlways = true;
  	}
  	
  	// make options.debug
  	if (this.options.debug === true) {
  		this.options.debug = 'all';
  	} else if (Array.isArray(this.options.debug)) {
  		(function() {
  			var d = {};
  			$.each(self.options.debug, function() {
  				d[this] = true;
  			});
  			self.options.debug = d;
  		})();
  	} else {
  		this.options.debug = false;
  	}
  	
  	/**
  	 * Original functions evacuated by conflict check
  	 * 
  	 * @type Object
  	 */
  	this.noConflicts = {};
  	
  	/**
  	 * Check and save conflicts with bootstrap etc
  	 * 
  	 * @type Function
  	 */
  	this.noConflict = function() {
  		$.each(conflictChecks, function(i, p) {
  			if ($.fn[p] && typeof $.fn[p].noConflict === 'function') {
  				self.noConflicts[p] = $.fn[p].noConflict();
  			}
  		});
  	};
  	// do check conflict
  	this.noConflict();
  	
  	/**
  	 * Is elFinder over CORS
  	 *
  	 * @type Boolean
  	 **/
  	this.isCORS = false;
  	
  	// configure for CORS
  	(function(){
  		if (typeof self.options.cors !== 'undefined' && self.options.cors !== null) {
  			self.isCORS = self.options.cors? true : false;
  		} else {
  			var parseUrl = document.createElement('a'),
  				parseUploadUrl,
  				selfProtocol = window.location.protocol,
  				portReg = function(protocol) {
  					protocol = (!protocol || protocol === ':')? selfProtocol : protocol;
  					return protocol === 'https:'? /\:443$/ : /\:80$/;
  				},
  				selfHost = window.location.host.replace(portReg(selfProtocol), '');
  			parseUrl.href = opts.url;
  			if (opts.urlUpload && (opts.urlUpload !== opts.url)) {
  				parseUploadUrl = document.createElement('a');
  				parseUploadUrl.href = opts.urlUpload;
  			}
  			if (selfHost !== parseUrl.host.replace(portReg(parseUrl.protocol), '')
  				|| (parseUrl.protocol !== ':'&& parseUrl.protocol !== '' && (selfProtocol !== parseUrl.protocol))
  				|| (parseUploadUrl && 
  					(selfHost !== parseUploadUrl.host.replace(portReg(parseUploadUrl.protocol), '')
  					|| (parseUploadUrl.protocol !== ':' && parseUploadUrl.protocol !== '' && (selfProtocol !== parseUploadUrl.protocol))
  					)
  				)
  			) {
  				self.isCORS = true;
  			}
  		}
  		if (self.isCORS) {
  			if (!$.isPlainObject(self.options.customHeaders)) {
  				self.options.customHeaders = {};
  			}
  			if (!$.isPlainObject(self.options.xhrFields)) {
  				self.options.xhrFields = {};
  			}
  			self.options.requestType = 'post';
  			self.options.customHeaders['X-Requested-With'] = 'XMLHttpRequest';
  			self.options.xhrFields['withCredentials'] = true;
  		}
  	})();
  
  	/**
  	 * Ajax request type
  	 *
  	 * @type String
  	 * @default "get"
  	 **/
  	this.requestType = /^(get|post)$/i.test(this.options.requestType) ? this.options.requestType.toLowerCase() : 'get';
  	
  	// set `requestMaxConn` by option
  	requestMaxConn = Math.max(parseInt(this.options.requestMaxConn), 1);
  	
  	/**
  	 * Custom data that given as options
  	 * 
  	 * @type Object
  	 * @default {}
  	 */
  	this.optsCustomData = $.isPlainObject(this.options.customData) ? this.options.customData : {};
  
  	/**
  	 * Any data to send across every ajax request
  	 *
  	 * @type Object
  	 * @default {}
  	 **/
  	this.customData = Object.assign({}, this.optsCustomData);
  
  	/**
  	 * Previous custom data from connector
  	 * 
  	 * @type Object|null
  	 */
  	this.prevCustomData = null;
  
  	/**
  	 * Any custom headers to send across every ajax request
  	 *
  	 * @type Object
  	 * @default {}
  	*/
  	this.customHeaders = $.isPlainObject(this.options.customHeaders) ? this.options.customHeaders : {};
  
  	/**
  	 * Any custom xhrFields to send across every ajax request
  	 *
  	 * @type Object
  	 * @default {}
  	 */
  	this.xhrFields = $.isPlainObject(this.options.xhrFields) ? this.options.xhrFields : {};
  
  	/**
  	 * Replace XMLHttpRequest.prototype.send to extended function for 3rd party libs XHR request etc.
  	 * 
  	 * @type Function
  	 */
  	this.replaceXhrSend = function() {
  		if (! savedXhrSend) {
  			savedXhrSend = XMLHttpRequest.prototype.send;
  		}
  		XMLHttpRequest.prototype.send = function() {
  			var xhr = this;
  			// set request headers
  			if (self.customHeaders) {
  				$.each(self.customHeaders, function(key) {
  					xhr.setRequestHeader(key, this);
  				});
  			}
  			// set xhrFields
  			if (self.xhrFields) {
  				$.each(self.xhrFields, function(key) {
  					if (key in xhr) {
  						xhr[key] = this;
  					}
  				});
  			}
  			return savedXhrSend.apply(this, arguments);
  		};
  	};
  	
  	/**
  	 * Restore saved original XMLHttpRequest.prototype.send
  	 * 
  	 * @type Function
  	 */
  	this.restoreXhrSend = function() {
  		savedXhrSend && (XMLHttpRequest.prototype.send = savedXhrSend);
  	};
  
  	/**
  	 * command names for into queue for only cwd requests
  	 * these commands aborts before `open` request
  	 *
  	 * @type Array
  	 * @default ['tmb', 'parents']
  	 */
  	this.abortCmdsOnOpen = this.options.abortCmdsOnOpen || ['tmb', 'parents'];
  
  	/**
  	 * ID. Required to create unique cookie name
  	 *
  	 * @type String
  	 **/
  	this.id = id;
  	
  	/**
  	 * ui.nav id prefix
  	 * 
  	 * @type String
  	 */
  	this.navPrefix = 'nav' + (elFinder.prototype.uniqueid? elFinder.prototype.uniqueid : '') + '-';
  	
  	/**
  	 * ui.cwd id prefix
  	 * 
  	 * @type String
  	 */
  	this.cwdPrefix = elFinder.prototype.uniqueid? ('cwd' + elFinder.prototype.uniqueid + '-') : '';
  	
  	// Increment elFinder.prototype.uniqueid
  	++elFinder.prototype.uniqueid;
  	
  	/**
  	 * URL to upload files
  	 *
  	 * @type String
  	 **/
  	this.uploadURL = opts.urlUpload || opts.url;
  	
  	/**
  	 * Events namespace
  	 *
  	 * @type String
  	 **/
  	this.namespace = namespace;
  
  	/**
  	 * Today timestamp
  	 *
  	 * @type Number
  	 **/
  	this.today = (new Date(date.getFullYear(), date.getMonth(), date.getDate())).getTime()/1000;
  	
  	/**
  	 * Yesterday timestamp
  	 *
  	 * @type Number
  	 **/
  	this.yesterday = this.today - 86400;
  	
  	utc = this.options.UTCDate ? 'UTC' : '';
  	
  	this.getHours    = 'get'+utc+'Hours';
  	this.getMinutes  = 'get'+utc+'Minutes';
  	this.getSeconds  = 'get'+utc+'Seconds';
  	this.getDate     = 'get'+utc+'Date';
  	this.getDay      = 'get'+utc+'Day';
  	this.getMonth    = 'get'+utc+'Month';
  	this.getFullYear = 'get'+utc+'FullYear';
  	
  	/**
  	 * elFinder node z-index (auto detect on elFinder load)
  	 *
  	 * @type null | Number
  	 **/
  	this.zIndex;
  
  	/**
  	 * Current search status
  	 * 
  	 * @type Object
  	 */
  	this.searchStatus = {
  		state  : 0, // 0: search ended, 1: search started, 2: in search result
  		query  : '',
  		target : '',
  		mime   : '',
  		mixed  : false, // in multi volumes search: false or Array that target volume ids
  		ininc  : false // in incremental search
  	};
  
  	/**
  	 * Method to store/fetch data
  	 *
  	 * @type Function
  	 **/
  	this.storage = (function() {
  		try {
  			if ('localStorage' in window && window['localStorage'] !== null) {
  				if (self.UA.Safari) {
  					// check for Mac/iOS safari private browsing mode
  					window.localStorage.setItem('elfstoragecheck', 1);
  					window.localStorage.removeItem('elfstoragecheck');
  				}
  				return self.localStorage;
  			} else {
  				return self.cookie;
  			}
  		} catch (e) {
  			return self.cookie;
  		}
  	})();
  
  	/**
  	 * Interface language
  	 *
  	 * @type String
  	 * @default "en"
  	 **/
  	this.lang = this.storage('lang') || this.options.lang;
  	if (this.lang === 'jp') {
  		this.lang = this.options.lang = 'ja';
  	}
  
  	this.viewType = this.storage('view') || this.options.defaultView || 'icons';
  
  	this.sortType = this.storage('sortType') || this.options.sortType || 'name';
  	
  	this.sortOrder = this.storage('sortOrder') || this.options.sortOrder || 'asc';
  
  	this.sortStickFolders = this.storage('sortStickFolders');
  	if (this.sortStickFolders === null) {
  		this.sortStickFolders = !!this.options.sortStickFolders;
  	} else {
  		this.sortStickFolders = !!this.sortStickFolders;
  	}
  
  	this.sortAlsoTreeview = this.storage('sortAlsoTreeview');
  	if (this.sortAlsoTreeview === null) {
  		this.sortAlsoTreeview = !!this.options.sortAlsoTreeview;
  	} else {
  		this.sortAlsoTreeview = !!this.sortAlsoTreeview;
  	}
  
  	this.sortRules = $.extend(true, {}, this._sortRules, this.options.sortRules);
  	
  	$.each(this.sortRules, function(name, method) {
  		if (typeof method != 'function') {
  			delete self.sortRules[name];
  		} 
  	});
  	
  	this.compare = $.proxy(this.compare, this);
  	
  	/**
  	 * Delay in ms before open notification dialog
  	 *
  	 * @type Number
  	 * @default 500
  	 **/
  	this.notifyDelay = this.options.notifyDelay > 0 ? parseInt(this.options.notifyDelay) : 500;
  	
  	/**
  	 * Dragging UI Helper object
  	 *
  	 * @type jQuery | null
  	 **/
  	this.draggingUiHelper = null;
  	
  	/**
  	 * Base droppable options
  	 *
  	 * @type Object
  	 **/
  	this.droppable = {
  		greedy     : true,
  		tolerance  : 'pointer',
  		accept     : '.elfinder-cwd-file-wrapper,.elfinder-navbar-dir,.elfinder-cwd-file,.elfinder-cwd-filename',
  		hoverClass : this.res('class', 'adroppable'),
  		classes    : { // Deprecated hoverClass jQueryUI>=1.12.0
  			'ui-droppable-hover': this.res('class', 'adroppable')
  		},
  		autoDisable: true, // elFinder original, see jquery.elfinder.js
  		drop : function(e, ui) {
  			var dst     = $(this),
  				targets = $.grep(ui.helper.data('files')||[], function(h) { return h? true : false; }),
  				result  = [],
  				dups    = [],
  				faults  = [],
  				isCopy  = ui.helper.hasClass('elfinder-drag-helper-plus'),
  				c       = 'class',
  				cnt, hash, i, h;
  			
  			if (typeof e.button === 'undefined' || ui.helper.data('namespace') !== namespace || ! self.insideWorkzone(e.pageX, e.pageY)) {
  				return false;
  			}
  			if (dst.hasClass(self.res(c, 'cwdfile'))) {
  				hash = self.cwdId2Hash(dst.attr('id'));
  			} else if (dst.hasClass(self.res(c, 'navdir'))) {
  				hash = self.navId2Hash(dst.attr('id'));
  			} else {
  				hash = cwd;
  			}
  
  			cnt = targets.length;
  			
  			while (cnt--) {
  				h = targets[cnt];
  				// ignore drop into itself or in own location
  				if (h != hash && files[h].phash != hash) {
  					result.push(h);
  				} else {
  					((isCopy && h !== hash && files[hash].write)? dups : faults).push(h);
  				}
  			}
  			
  			if (faults.length) {
  				return false;
  			}
  			
  			ui.helper.data('droped', true);
  			
  			if (dups.length) {
  				ui.helper.hide();
  				self.exec('duplicate', dups, {_userAction: true});
  			}
  			
  			if (result.length) {
  				ui.helper.hide();
  				self.clipboard(result, !isCopy);
  				self.exec('paste', hash, {_userAction: true}, hash).always(function(){
  					self.clipboard([]);
  					self.trigger('unlockfiles', {files : targets});
  				});
  				self.trigger('drop', {files : targets});
  			}
  		}
  	};
  	
  	/**
  	 * Return true if filemanager is active
  	 *
  	 * @return Boolean
  	 **/
  	this.enabled = function() {
  		return enabled && this.visible();
  	};
  	
  	/**
  	 * Return true if filemanager is visible
  	 *
  	 * @return Boolean
  	 **/
  	this.visible = function() {
  		return node[0].elfinder && node.is(':visible');
  	};
  	
  	/**
  	 * Return file is root?
  	 * 
  	 * @param  Object  target file object
  	 * @return Boolean
  	 */
  	this.isRoot = function(file) {
  		return (file.isroot || ! file.phash)? true : false;
  	};
  	
  	/**
  	 * Return root dir hash for current working directory
  	 * 
  	 * @param  String   target hash
  	 * @param  Boolean  include fake parent (optional)
  	 * @return String
  	 */
  	this.root = function(hash, fake) {
  		hash = hash || cwd;
  		var dir, i;
  		
  		if (! fake) {
  			$.each(self.roots, function(id, rhash) {
  				if (hash.indexOf(id) === 0) {
  					dir = rhash;
  					return false;
  				}
  			});
  			if (dir) {
  				return dir;
  			}
  		}
  		
  		dir = files[hash];
  		while (dir && dir.phash && (fake || ! dir.isroot)) {
  			dir = files[dir.phash];
  		}
  		if (dir) {
  			return dir.hash;
  		}
  		
  		while (i in files && files.hasOwnProperty(i)) {
  			dir = files[i];
  			if (dir.mime === 'directory' && !dir.phash && dir.read) {
  				return dir.hash;
  			}
  		}
  		
  		return '';
  	};
  	
  	/**
  	 * Return current working directory info
  	 * 
  	 * @return Object
  	 */
  	this.cwd = function() {
  		return files[cwd] || {};
  	};
  	
  	/**
  	 * Return required cwd option
  	 * 
  	 * @param  String  option name
  	 * @param  String  target hash (optional)
  	 * @return mixed
  	 */
  	this.option = function(name, target) {
  		var res, item;
  		target = target || cwd;
  		if (self.optionsByHashes[target] && typeof self.optionsByHashes[target][name] !== 'undefined') {
  			return self.optionsByHashes[target][name];
  		}
  		if (self.hasVolOptions && cwd !== target && (!(item = self.file(target)) || item.phash !== cwd)) {
  			res = '';
  			$.each(self.volOptions, function(id, opt) {
  				if (target.indexOf(id) === 0) {
  					res = opt[name] || '';
  					return false;
  				}
  			});
  			return res;
  		} else {
  			return cwdOptions[name] || '';
  		}
  	};
  	
  	/**
  	 * Return disabled commands by each folder
  	 * 
  	 * @param  Array  target hashes
  	 * @return Array
  	 */
  	this.getDisabledCmds = function(targets, flip) {
  		var disabled = {'hidden': true};
  		if (! Array.isArray(targets)) {
  			targets = [ targets ];
  		}
  		$.each(targets, function(i, h) {
  			var disCmds = self.option('disabledFlip', h);
  			if (disCmds) {
  				Object.assign(disabled, disCmds);
  			}
  		});
  		return flip? disabled : Object.keys(disabled);
  	};
  	
  	/**
  	 * Return file data from current dir or tree by it's hash
  	 * 
  	 * @param  String  file hash
  	 * @return Object
  	 */
  	this.file = function(hash) { 
  		return hash? files[hash] : void(0); 
  	};
  	
  	/**
  	 * Return all cached files
  	 * 
  	 * @param  String  parent hash
  	 * @return Object
  	 */
  	this.files = function(phash) {
  		var items = {};
  		if (phash) {
  			if (!ownFiles[phash]) {
  				return {};
  			}
  			$.each(ownFiles[phash], function(h) {
  				if (files[h]) {
  					items[h] = files[h];
  				} else {
  					delete ownFiles[phash][h];
  				}
  			});
  			return Object.assign({}, items);
  		}
  		return Object.assign({}, files);
  	};
  	
  	/**
  	 * Return list of file parents hashes include file hash
  	 * 
  	 * @param  String  file hash
  	 * @return Array
  	 */
  	this.parents = function(hash) {
  		var parents = [],
  			dir;
  		
  		while (hash && (dir = this.file(hash))) {
  			parents.unshift(dir.hash);
  			hash = dir.phash;
  		}
  		return parents;
  	};
  	
  	this.path2array = function(hash, i18) {
  		var file, 
  			path = [];
  			
  		while (hash) {
  			if ((file = files[hash]) && file.hash) {
  				path.unshift(i18 && file.i18 ? file.i18 : file.name);
  				hash = file.isroot? null : file.phash;
  			} else {
  				path = [];
  				break;
  			}
  		}
  			
  		return path;
  	};
  	
  	/**
  	 * Return file path or Get path async with jQuery.Deferred
  	 * 
  	 * @param  Object  file
  	 * @param  Boolean i18
  	 * @param  Object  asyncOpt
  	 * @return String|jQuery.Deferred
  	 */
  	this.path = function(hash, i18, asyncOpt) { 
  		var path = files[hash] && files[hash].path
  			? files[hash].path
  			: this.path2array(hash, i18).join(cwdOptions.separator);
  		if (! asyncOpt || ! files[hash]) {
  			return path;
  		} else {
  			asyncOpt = Object.assign({notify: {type : 'parents', cnt : 1, hideCnt : true}}, asyncOpt);
  			
  			var dfd    = $.Deferred(),
  				notify = asyncOpt.notify,
  				noreq  = false,
  				req    = function() {
  					self.request({
  						data : {cmd : 'parents', target : files[hash].phash},
  						notify : notify,
  						preventFail : true
  					})
  					.done(done)
  					.fail(function() {
  						dfd.reject();
  					});
  				},
  				done   = function() {
  					self.one('parentsdone', function() {
  						path = self.path(hash, i18);
  						if (path === '' && noreq) {
  							//retry with request
  							noreq = false;
  							req();
  						} else {
  							if (notify) {
  								clearTimeout(ntftm);
  								notify.cnt = -(parseInt(notify.cnt || 0));
  								self.notify(notify);
  							}
  							dfd.resolve(path);
  						}
  					});
  				},
  				ntftm;
  		
  			if (path) {
  				return dfd.resolve(path);
  			} else {
  				if (self.ui['tree']) {
  					// try as no request
  					if (notify) {
  						ntftm = setTimeout(function() {
  							self.notify(notify);
  						}, self.notifyDelay);
  					}
  					noreq = true;
  					done(true);
  				} else {
  					req();
  				}
  				return dfd;
  			}
  		}
  	};
  	
  	/**
  	 * Return file url if set
  	 * 
  	 * @param  String  file hash
  	 * @param  Object  Options
  	 * @return String
  	 */
  	this.url = function(hash, o) {
  		var file   = files[hash],
  			opts   = o || {},
  			async  = opts.async || false,
  			temp   = opts.temporary || false,
  			dfrd   = async? $.Deferred() : null,
  			getUrl = function(url) {
  				if (url) {
  					return url;
  				}
  				if (file.url) {
  					return file.url;
  				}
  				
  				baseUrl = self.option('url', file.phash || file.hash);
  				
  				if (baseUrl) {
  					return baseUrl + $.map(self.path2array(hash), function(n) { return encodeURIComponent(n); }).slice(1).join('/');
  				}
  
  				var params = Object.assign({}, self.customData, {
  					cmd: 'file',
  					target: file.hash
  				});
  				if (self.oldAPI) {
  					params.cmd = 'open';
  					params.current = file.phash;
  				}
  				return self.options.url + (self.options.url.indexOf('?') === -1 ? '?' : '&') + $.param(params, true);
  			}, 
  			baseUrl, res;
  		
  		if (!file || !file.read) {
  			return async? dfrd.resolve('') : '';
  		}
  		
  		if (file.url == '1') {
  			this.request({
  				data : { cmd : 'url', target : hash, options : { temporary: temp? 1 : 0 } },
  				preventDefault : true,
  				options: {async: async},
  				notify: async? {type : temp? 'file' : 'url', cnt : 1, hideCnt : true} : {}
  			})
  			.done(function(data) {
  				file.url = data.url || '';
  			})
  			.fail(function() {
  				file.url = '';
  			})
  			.always(function() {
  				var url;
  				if (file.url && temp) {
  					url = file.url;
  					file.url = '1'; // restore
  				}
  				if (async) {
  					dfrd.resolve(getUrl(url));
  				} else {
  					return getUrl(url);
  				}
  			});
  		} else {
  			if (async) {
  				dfrd.resolve(getUrl());
  			} else {
  				return getUrl();
  			}
  		}
  		
  		if (async) {
  			return dfrd;
  		}
  	};
  	
  	/**
  	 * Return file url for open in elFinder
  	 * 
  	 * @param  String  file hash
  	 * @param  Boolean for download link
  	 * @return String
  	 */
  	this.openUrl = function(hash, download) {
  		var file = files[hash],
  			url  = '';
  		
  		if (!file || !file.read) {
  			return '';
  		}
  		
  		if (!download) {
  			if (file.url) {
  				if (file.url != 1) {
  					url = file.url;
  				}
  			} else if (cwdOptions.url && file.hash.indexOf(self.cwd().volumeid) === 0) {
  				url = cwdOptions.url + $.map(this.path2array(hash), function(n) { return encodeURIComponent(n); }).slice(1).join('/');
  			}
  			if (url) {
  				url += (url.match(/\?/)? '&' : '?') + '_'.repeat((url.match(/[\?&](_+)t=/g) || ['&t=']).sort().shift().match(/[\?&](_*)t=/)[1].length + 1) + 't=' + (file.ts || parseInt(+new Date()/1000));
  				return url;
  			}
  		}
  		
  		url = this.options.url;
  		url = url + (url.indexOf('?') === -1 ? '?' : '&')
  			+ (this.oldAPI ? 'cmd=open&current='+file.phash : 'cmd=file')
  			+ '&target=' + file.hash
  			+ '&_t=' + (file.ts || parseInt(+new Date()/1000));
  		
  		if (download) {
  			url += '&download=1';
  		}
  		
  		$.each(this.customData, function(key, val) {
  			url += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(val);
  		});
  		
  		return url;
  	};
  	
  	/**
  	 * Return thumbnail url
  	 * 
  	 * @param  Object  file object
  	 * @return String
  	 */
  	this.tmb = function(file) {
  		var tmbUrl, tmbCrop,
  			cls    = 'elfinder-cwd-bgurl',
  			url    = '';
  
  		if ($.isPlainObject(file)) {
  			if (self.searchStatus.state && file.hash.indexOf(self.cwd().volumeid) !== 0) {
  				tmbUrl = self.option('tmbUrl', file.hash);
  				tmbCrop = self.option('tmbCrop', file.hash);
  			} else {
  				tmbUrl = cwdOptions['tmbUrl'];
  				tmbCrop = cwdOptions['tmbCrop'];
  			}
  			if (tmbCrop) {
  				cls += ' elfinder-cwd-bgurl-crop';
  			}
  			if (tmbUrl === 'self' && file.mime.indexOf('image/') === 0) {
  				url = self.openUrl(file.hash);
  				cls += ' elfinder-cwd-bgself';
  			} else if ((self.oldAPI || tmbUrl) && file && file.tmb && file.tmb != 1) {
  				url = tmbUrl + file.tmb;
  			} else if (self.newAPI && file && file.tmb && file.tmb != 1) {
  				url = file.tmb;
  			}
  			if (url) {
  				if (file.ts && tmbUrl !== 'self') {
  					url += (url.match(/\?/)? '&' : '?') + '_t=' + file.ts;
  				}
  				return { url: url, className: cls };
  			}
  		}
  		
  		return false;
  	};
  	
  	/**
  	 * Return selected files hashes
  	 *
  	 * @return Array
  	 **/
  	this.selected = function() {
  		return selected.slice(0);
  	};
  	
  	/**
  	 * Return selected files info
  	 * 
  	 * @return Array
  	 */
  	this.selectedFiles = function() {
  		return $.map(selected, function(hash) { return files[hash] ? Object.assign({}, files[hash]) : null; });
  	};
  	
  	/**
  	 * Return true if file with required name existsin required folder
  	 * 
  	 * @param  String  file name
  	 * @param  String  parent folder hash
  	 * @return Boolean
  	 */
  	this.fileByName = function(name, phash) {
  		var hash;
  	
  		for (hash in files) {
  			if (files.hasOwnProperty(hash) && files[hash].phash == phash && files[hash].name == name) {
  				return files[hash];
  			}
  		}
  	};
  	
  	/**
  	 * Valid data for required command based on rules
  	 * 
  	 * @param  String  command name
  	 * @param  Object  cammand's data
  	 * @return Boolean
  	 */
  	this.validResponse = function(cmd, data) {
  		return data.error || this.rules[this.rules[cmd] ? cmd : 'defaults'](data);
  	};
  	
  	/**
  	 * Return bytes from ini formated size
  	 * 
  	 * @param  String  ini formated size
  	 * @return Integer
  	 */
  	this.returnBytes = function(val) {
  		var last;
  		if (isNaN(val)) {
  			if (! val) {
  				val = '';
  			}
  			// for ex. 1mb, 1KB
  			val = val.replace(/b$/i, '');
  			last = val.charAt(val.length - 1).toLowerCase();
  			val = val.replace(/[tgmk]$/i, '');
  			if (last == 't') {
  				val = val * 1024 * 1024 * 1024 * 1024;
  			} else if (last == 'g') {
  				val = val * 1024 * 1024 * 1024;
  			} else if (last == 'm') {
  				val = val * 1024 * 1024;
  			} else if (last == 'k') {
  				val = val * 1024;
  			}
  			val = isNaN(val)? 0 : parseInt(val);
  		} else {
  			val = parseInt(val);
  			if (val < 1) val = 0;
  		}
  		return val;
  	};
  	
  	/**
  	 * Process ajax request.
  	 * Fired events :
  	 * @todo
  	 * @example
  	 * @todo
  	 * @return $.Deferred
  	 */
  	this.request = function(opts) { 
  		var self     = this,
  			o        = this.options,
  			dfrd     = $.Deferred(),
  			// request ID
  			reqId    = (+ new Date()).toString(16) + Math.floor(1000 * Math.random()).toString(16), 
  			// request data
  			data     = Object.assign({}, self.customData, {mimes : o.onlyMimes}, opts.data || opts),
  			// command name
  			cmd      = data.cmd,
  			// request type is binary
  			isBinary = (opts.options || {}).dataType === 'binary',
  			// current cmd is "open"
  			isOpen   = (!opts.asNotOpen && cmd === 'open'),
  			// call default fail callback (display error dialog) ?
  			deffail  = !(isBinary || opts.preventDefault || opts.preventFail),
  			// call default success callback ?
  			defdone  = !(isBinary || opts.preventDefault || opts.preventDone),
  			// options for notify dialog
  			notify   = Object.assign({}, opts.notify),
  			// make cancel button
  			cancel   = !!opts.cancel,
  			// do not normalize data - return as is
  			raw      = isBinary || !!opts.raw,
  			// sync files on request fail
  			syncOnFail = opts.syncOnFail,
  			// use lazy()
  			lazy     = !!opts.lazy,
  			// prepare function before done()
  			prepare  = opts.prepare,
  			// navigate option object when cmd done
  			navigate = opts.navigate,
  			// open notify dialog timeout
  			timeout,
  			// use browser cache
  			useCache = (opts.options || {}).cache,
  			// request options
  			options = Object.assign({
  				url      : o.url,
  				async    : true,
  				type     : this.requestType,
  				dataType : 'json',
  				cache    : (self.api >= 2.1029), // api >= 2.1029 has unique request ID
  				data     : data,
  				headers  : this.customHeaders,
  				xhrFields: this.xhrFields
  			}, opts.options || {}),
  			/**
  			 * Default success handler. 
  			 * Call default data handlers and fire event with command name.
  			 *
  			 * @param Object  normalized response data
  			 * @return void
  			 **/
  			done = function(data) {
  				data.warning && self.error(data.warning);
  				
  				if (isOpen) {
  					open(data);
  				} else {
  					self.updateCache(data);
  				}
  				
  				data.changed && data.changed.length && change(data.changed);
  				
  				self.lazy(function() {
  					// fire some event to update cache/ui
  					data.removed && data.removed.length && self.remove(data);
  					data.added   && data.added.length   && self.add(data);
  					data.changed && data.changed.length && self.change(data);
  				}).then(function() {
  					// fire event with command name
  					return self.lazy(function() {
  						self.trigger(cmd, data, false);
  					});
  				}).then(function() {
  					// fire event with command name + 'done'
  					return self.lazy(function() {
  						self.trigger(cmd + 'done');
  					});
  				}).then(function() {
  					// force update content
  					data.sync && self.sync();
  				});
  			},
  			/**
  			 * Request error handler. Reject dfrd with correct error message.
  			 *
  			 * @param jqxhr  request object
  			 * @param String request status
  			 * @return void
  			 **/
  			error = function(xhr, status) {
  				var error, data, 
  					d = self.options.debug;
  				
  				switch (status) {
  					case 'abort':
  						error = xhr.quiet ? '' : ['errConnect', 'errAbort'];
  						break;
  					case 'timeout':	    
  						error = ['errConnect', 'errTimeout'];
  						break;
  					case 'parsererror': 
  						error = ['errResponse', 'errDataNotJSON'];
  						if (xhr.responseText) {
  							if (! cwd || (d && (d === 'all' || d['backend-error']))) {
  								error.push(xhr.responseText);
  							}
  						}
  						break;
  					default:
  						if (xhr.responseText) {
  							// check responseText, Is that JSON?
  							try {
  								data = JSON.parse(xhr.responseText);
  								if (data && data.error) {
  									error = data.error;
  								}
  							} catch(e) {}
  						}
  						if (! error) {
  							if (xhr.status == 403) {
  								error = ['errConnect', 'errAccess', 'HTTP error ' + xhr.status];
  							} else if (xhr.status == 404) {
  								error = ['errConnect', 'errNotFound', 'HTTP error ' + xhr.status];
  							} else if (xhr.status >= 500) {
  								error = ['errResponse', 'errServerError', 'HTTP error ' + xhr.status];
  							} else {
  								if (xhr.status == 414 && options.type === 'get') {
  									// retry by POST method
  									options.type = 'post';
  									self.abortXHR(xhr);
  									dfrd.xhr = xhr = self.transport.send(options).fail(error).done(success);
  									return;
  								}
  								error = xhr.quiet ? '' : ['errConnect', 'HTTP error ' + xhr.status];
  							} 
  						}
  				}
  				
  				self.trigger(cmd + 'done');
  				dfrd.reject(error, xhr, status);
  			},
  			/**
  			 * Request success handler. Valid response data and reject/resolve dfrd.
  			 *
  			 * @param Object  response data
  			 * @param String request status
  			 * @return void
  			 **/
  			success = function(response) {
  				var d = self.options.debug;
  				
  				// Set currrent request command name
  				self.currentReqCmd = cmd;
  				
  				if (response.debug && (!d || d !== 'all')) {
  					if (!d) {
  						d = self.options.debug = {};
  					}
  					d['backend-error'] = true;
  					d['warning'] = true;
  				}
  				
  				if (raw) {
  					self.abortXHR(xhr);
  					response && response.debug && self.debug('backend-debug', response);
  					return dfrd.resolve(response);
  				}
  				
  				if (!response) {
  					return dfrd.reject(['errResponse', 'errDataEmpty'], xhr, response);
  				} else if (!$.isPlainObject(response)) {
  					return dfrd.reject(['errResponse', 'errDataNotJSON'], xhr, response);
  				} else if (response.error) {
  					if (isOpen) {
  						// check leafRoots
  						$.each(self.leafRoots, function(phash, roots) {
  							self.leafRoots[phash] = $.grep(roots, function(h) { return h !== data.target; });
  						});
  					}
  					return dfrd.reject(response.error, xhr, response);
  				}
  				
  				var resolve = function() {
  					var pushLeafRoots = function(name) {
  						if (self.leafRoots[data.target] && response[name]) {
  							$.each(self.leafRoots[data.target], function(i, h) {
  								var root;
  								if (root = self.file(h)) {
  									response[name].push(root);
  								}
  							});
  						}
  					},
  					setTextMimes = function() {
  						self.textMimes = {};
  						$.each(self.res('mimes', 'text'), function() {
  							self.textMimes[this] = true;
  						});
  					},
  					actionTarget;
  					
  					if (isOpen) {
  						pushLeafRoots('files');
  					} else if (cmd === 'tree') {
  						pushLeafRoots('tree');
  					}
  					
  					response = self.normalize(response);
  					
  					if (!self.validResponse(cmd, response)) {
  						return dfrd.reject((response.norError || 'errResponse'), xhr, response);
  					}
  					
  					if (isOpen) {
  						if (!self.api) {
  							self.api    = response.api || 1;
  							if (self.api == '2.0' && typeof response.options.uploadMaxSize !== 'undefined') {
  								self.api = '2.1';
  							}
  							self.newAPI = self.api >= 2;
  							self.oldAPI = !self.newAPI;
  						}
  						
  						if (response.textMimes && Array.isArray(response.textMimes)) {
  							self.resources.mimes.text = response.textMimes;
  							setTextMimes();
  						}
  						!self.textMimes && setTextMimes();
  						
  						if (response.options) {
  							cwdOptions = Object.assign({}, cwdOptionsDefault, response.options);
  						}
  
  						if (response.netDrivers) {
  							self.netDrivers = response.netDrivers;
  						}
  
  						if (response.maxTargets) {
  							self.maxTargets = response.maxTargets;
  						}
  
  						if (!!data.init) {
  							self.uplMaxSize = self.returnBytes(response.uplMaxSize);
  							self.uplMaxFile = !!response.uplMaxFile? parseInt(response.uplMaxFile) : 20;
  						}
  					}
  
  					if (typeof prepare === 'function') {
  						prepare(response);
  					}
  					
  					if (navigate) {
  						actionTarget = navigate.target || 'added';
  						if (response[actionTarget] && response[actionTarget].length) {
  							self.one(cmd + 'done', function() {
  								var targets  = response[actionTarget],
  									newItems = self.findCwdNodes(targets),
  									inCwdHashes = function() {
  										var cwdHash = self.cwd().hash;
  										return $.map(targets, function(f) { return (f.phash && cwdHash === f.phash)? f.hash : null; });
  									},
  									hashes   = inCwdHashes(),
  									makeToast  = function(t) {
  										var node = void(0),
  											data = t.action? t.action.data : void(0),
  											cmd, msg, done;
  										if ((data || hashes.length) && t.action && (msg = t.action.msg) && (cmd = t.action.cmd) && (!t.action.cwdNot || t.action.cwdNot !== self.cwd().hash)) {
  											done = t.action.done;
  											data = t.action.data;
  											node = $('<div/>')
  												.append(
  													$('<button type="button" class="ui-button ui-widget ui-state-default ui-corner-all elfinder-tabstop"><span class="ui-button-text">'
  														+self.i18n(msg)
  														+'</span></button>')
  													.on('mouseenter mouseleave', function(e) { 
  														$(this).toggleClass('ui-state-hover', e.type == 'mouseenter');
  													})
  													.on('click', function() {
  														self.exec(cmd, data || hashes, {_userAction: true, _currentType: 'toast', _currentNode: $(this) });
  														if (done) {
  															self.one(cmd+'done', function() {
  																if (typeof done === 'function') {
  																	done();
  																} else if (done === 'select') {
  																	self.trigger('selectfiles', {files : inCwdHashes()});
  																}
  															});
  														}
  													})
  												);
  										}
  										delete t.action;
  										t.extNode = node;
  										return t;
  									};
  								
  								if (! navigate.toast) {
  									navigate.toast = {};
  								}
  								
  								!navigate.noselect && self.trigger('selectfiles', {files : self.searchStatus.state > 1 ? $.map(targets, function(f) { return f.hash; }) : hashes});
  								
  								if (newItems.length) {
  									if (!navigate.noscroll) {
  										newItems.first().trigger('scrolltoview', {blink : false});
  										self.resources.blink(newItems, 'lookme');
  									}
  									if ($.isPlainObject(navigate.toast.incwd)) {
  										self.toast(makeToast(navigate.toast.incwd));
  									}
  								} else {
  									if ($.isPlainObject(navigate.toast.inbuffer)) {
  										self.toast(makeToast(navigate.toast.inbuffer));
  									}
  								}
  							});
  						}
  					}
  					
  					dfrd.resolve(response);
  					
  					response.debug && self.debug('backend-debug', response);
  				};
  				self.abortXHR(xhr);
  				lazy? self.lazy(resolve) : resolve();
  			},
  			xhr, _xhr,
  			xhrAbort = function(e) {
  				if (xhr && xhr.state() === 'pending') {
  					self.abortXHR(xhr, { quiet: true , abort: true });
  					if (!e || (e.type !== 'unload' && e.type !== 'destroy')) {
  						self.autoSync();
  					}
  				}
  			},
  			abort = function(e){
  				self.trigger(cmd + 'done');
  				if (e.type == 'autosync') {
  					if (e.data.action != 'stop') return;
  				} else if (e.type != 'unload' && e.type != 'destroy' && e.type != 'openxhrabort') {
  					if (!e.data.added || !e.data.added.length) {
  						return;
  					}
  				}
  				xhrAbort(e);
  			},
  			request = function(mode) {
  				var queueAbort = function() {
  					syncOnFail = false;
  					dfrd.reject();
  				};
  				
  				if (mode) {
  					if (mode === 'cmd') {
  						return cmd;
  					}
  				}
  				
  				if (isOpen) {
  					if (requestQueueSkipOpen) {
  						return dfrd.reject();
  					}
  					requestQueueSkipOpen = true;
  				}
  				
  				requestCnt++;
  				
  				dfrd.always(function() {
  					delete options.headers['X-elFinderReqid'];
  				}).fail(function(error, xhr, response) {
  					// unset this cmd queue when user canceling
  					if (error === 0) {
  						if (requestQueue.length) {
  							requestQueue = $.grep(requestQueue, function(req) {
  								return (req('cmd') === cmd) ? false : true;
  							});
  						}
  					}
  					xhrAbort();
  					if (isOpen) {
  						openDir = self.file(data.target);
  						openDir && openDir.volumeid && self.isRoot(openDir) && delete self.volumeExpires[openDir.volumeid];
  					}
  					self.trigger(cmd + 'fail', response);
  					if (error) {
  						deffail ? self.error(error) : self.debug('error', self.i18n(error));
  					}
  					syncOnFail && self.sync();
  				});
  
  				if (!cmd) {
  					syncOnFail = false;
  					return dfrd.reject('errCmdReq');
  				}
  				
  				if (self.maxTargets && data.targets && data.targets.length > self.maxTargets) {
  					syncOnFail = false;
  					return dfrd.reject(['errMaxTargets', self.maxTargets]);
  				}
  
  				defdone && dfrd.done(done);
  				
  				// quiet abort not completed "open" requests
  				if (isOpen) {
  					while ((_xhr = queue.pop())) {
  						_xhr.queueAbort();
  					}
  					if (cwd !== data.target) {
  						while ((_xhr = cwdQueue.pop())) {
  							_xhr.queueAbort();
  						}
  					}
  				}
  
  				// trigger abort autoSync for commands to add the item
  				if ($.inArray(cmd, (self.cmdsToAdd + ' autosync').split(' ')) !== -1) {
  					if (cmd !== 'autosync') {
  						self.autoSync('stop');
  						dfrd.always(function() {
  							self.autoSync();
  						});
  					}
  					self.trigger('openxhrabort');
  				}
  
  				delete options.preventFail;
  
  				if (self.api >= 2.1029) {
  					if (useCache) {
  						options.headers['X-elFinderReqid'] = reqId;
  					} else {
  						Object.assign(options.data, { reqid : reqId });
  					}
  				}
  				
  				// function for set value of this syncOnFail
  				dfrd.syncOnFail = function(state) {
  					syncOnFail = !!state;
  				};
  				
  				dfrd.xhr = xhr = self.transport.send(options).always(function() {
  					--requestCnt;
  					if (requestQueue.length) {
  						requestQueue.shift()();
  					} else {
  						requestQueueSkipOpen = false;
  					}
  				}).fail(error).done(success);
  				
  				if (self.api >= 2.1029) {
  					xhr._requestId = reqId;
  				}
  				
  				if (isOpen || (data.compare && cmd === 'info')) {
  					// regist function queueAbort
  					xhr.queueAbort = queueAbort;
  					// add autoSync xhr into queue
  					queue.unshift(xhr);
  					// bind abort()
  					data.compare && self.bind(self.cmdsToAdd + ' autosync openxhrabort', abort);
  					dfrd.always(function() {
  						var ndx = $.inArray(xhr, queue);
  						data.compare && self.unbind(self.cmdsToAdd + ' autosync openxhrabort', abort);
  						ndx !== -1 && queue.splice(ndx, 1);
  					});
  				} else if ($.inArray(cmd, self.abortCmdsOnOpen) !== -1) {
  					// regist function queueAbort
  					xhr.queueAbort = queueAbort;
  					// add "open" xhr, only cwd xhr into queue
  					cwdQueue.unshift(xhr);
  					dfrd.always(function() {
  						var ndx = $.inArray(xhr, cwdQueue);
  						ndx !== -1 && cwdQueue.splice(ndx, 1);
  					});
  				}
  				
  				// abort pending xhr on window unload or elFinder destroy
  				self.bind('unload destroy', abort);
  				dfrd.always(function() {
  					self.unbind('unload destroy', abort);
  				});
  				
  				return dfrd;
  			},
  			queueingRequest = function() {
  				// show notify
  				if (notify.type && notify.cnt) {
  					if (cancel) {
  						notify.cancel = dfrd;
  						opts.eachCancel && (notify.id = +new Date());
  					}
  					timeout = setTimeout(function() {
  						self.notify(notify);
  						dfrd.always(function() {
  							notify.cnt = -(parseInt(notify.cnt)||0);
  							self.notify(notify);
  						});
  					}, self.notifyDelay);
  					
  					dfrd.always(function() {
  						clearTimeout(timeout);
  					});
  				}
  				// queueing
  				if (isOpen) {
  					requestQueueSkipOpen = false;
  				}
  				if (requestCnt < requestMaxConn) {
  					// do request
  					return request();
  				} else {
  					if (isOpen) {
  						requestQueue.unshift(request);
  					} else {
  						requestQueue.push(request);
  					}
  					return dfrd;
  				}
  			},
  			bindData = {opts: opts, result: true},
  			openDir;
  		
  		// prevent request initial request is completed
  		if (!self.api && !data.init) {
  			syncOnFail = false;
  			return dfrd.reject();
  		}
  
  		// trigger "request.cmd" that callback be able to cancel request by substituting "false" for "event.data.result"
  		self.trigger('request.' + cmd, bindData, true);
  		
  		if (! bindData.result) {
  			self.trigger(cmd + 'done');
  			return dfrd.reject();
  		} else if (typeof bindData.result === 'object' && bindData.result.promise) {
  			bindData.result
  				.done(queueingRequest)
  				.fail(function() {
  					self.trigger(cmd + 'done');
  					dfrd.reject();
  				});
  			return dfrd;
  		}
  		
  		return queueingRequest();
  	};
  	
  	/**
  	 * Call cache()
  	 * Store info about files/dirs in "files" object.
  	 *
  	 * @param  Array  files
  	 * @return void
  	 */
  	this.cache = function(dataArray) {
  		if (! Array.isArray(dataArray)) {
  			dataArray = [ dataArray ];
  		}
  		cache(dataArray);
  	};
  	
  	/**
  	 * Update file object caches by respose data object
  	 * 
  	 * @param  Object  respose data object
  	 * @return void
  	 */
  	this.updateCache = function(data) {
  		if ($.isPlainObject(data)) {
  			data.files && data.files.length && cache(data.files, 'files');
  			data.tree && data.tree.length && cache(data.tree, 'tree');
  			data.removed && data.removed.length && remove(data.removed);
  			data.added && data.added.length && cache(data.added, 'add');
  			data.changed && data.changed.length && change(data.changed, 'change');
  		}
  	};
  	
  	/**
  	 * Compare current files cache with new files and return diff
  	 * 
  	 * @param  Array   new files
  	 * @param  String  target folder hash
  	 * @param  Array   exclude properties to compare
  	 * @return Object
  	 */
  	this.diff = function(incoming, onlydir, excludeProps) {
  		var raw       = {},
  			added     = [],
  			removed   = [],
  			changed   = [],
  			excludes  = null,
  			isChanged = function(hash) {
  				var l = changed.length;
  
  				while (l--) {
  					if (changed[l].hash == hash) {
  						return true;
  					}
  				}
  			};
  		
  		$.each(incoming, function(i, f) {
  			raw[f.hash] = f;
  		});
  		
  		// make excludes object
  		if (excludeProps && excludeProps.length) {
  			excludes = {};
  			$.each(excludeProps, function() {
  				excludes[this] = true;
  			});
  		}
  		
  		// find removed
  		$.each(files, function(hash, f) {
  			if (! raw[hash] && (! onlydir || f.phash === onlydir)) {
  				removed.push(hash);
  			}
  		});
  		
  		// compare files
  		$.each(raw, function(hash, file) {
  			var origin  = files[hash],
  				orgKeys = {},
  				chkKeyLen;
  
  			if (!origin) {
  				added.push(file);
  			} else {
  				// make orgKeys object
  				$.each(Object.keys(origin), function() {
  					orgKeys[this] = true;
  				});
  				$.each(file, function(prop) {
  					delete orgKeys[prop];
  					if (! excludes || ! excludes[prop]) {
  						if (file[prop] !== origin[prop]) {
  							changed.push(file);
  							orgKeys = {};
  							return false;
  						}
  					}
  				});
  				chkKeyLen = Object.keys(orgKeys).length;
  				if (chkKeyLen !== 0) {
  					if (excludes) {
  						$.each(orgKeys, function(prop) {
  							if (excludes[prop]) {
  								--chkKeyLen;
  							}
  						});
  					}
  					(chkKeyLen !== 0) && changed.push(file);
  				}
  			}
  		});
  		
  		// parents of removed dirs mark as changed (required for tree correct work)
  		$.each(removed, function(i, hash) {
  			var file  = files[hash], 
  				phash = file.phash;
  
  			if (phash 
  			&& file.mime == 'directory' 
  			&& $.inArray(phash, removed) === -1 
  			&& raw[phash] 
  			&& !isChanged(phash)) {
  				changed.push(raw[phash]);
  			}
  		});
  		
  		return {
  			added   : added,
  			removed : removed,
  			changed : changed
  		};
  	};
  	
  	/**
  	 * Sync content
  	 * 
  	 * @return jQuery.Deferred
  	 */
  	this.sync = function(onlydir, polling) {
  		this.autoSync('stop');
  		var self  = this,
  			compare = function(){
  				var c = '', cnt = 0, mtime = 0;
  				if (onlydir && polling) {
  					$.each(files, function(h, f) {
  						if (f.phash && f.phash === onlydir) {
  							++cnt;
  							mtime = Math.max(mtime, f.ts);
  						}
  						c = cnt+':'+mtime;
  					});
  				}
  				return c;
  			},
  			comp  = compare(),
  			dfrd  = $.Deferred().done(function() { self.trigger('sync'); }),
  			opts = [this.request({
  				data           : {cmd : 'open', reload : 1, target : cwd, tree : (! onlydir && this.ui.tree) ? 1 : 0, compare : comp},
  				preventDefault : true
  			})],
  			exParents = function() {
  				var parents = [],
  					curRoot = self.file(self.root(cwd)),
  					curId = curRoot? curRoot.volumeid : null,
  					phash = self.cwd().phash,
  					isroot,pdir;
  				
  				while(phash) {
  					if (pdir = self.file(phash)) {
  						if (phash.indexOf(curId) !== 0) {
  							parents.push( {target: phash, cmd: 'tree'} );
  							if (! self.isRoot(pdir)) {
  								parents.push( {target: phash, cmd: 'parents'} );
  							}
  							curRoot = self.file(self.root(phash));
  							curId = curRoot? curRoot.volumeid : null;
  						}
  						phash = pdir.phash;
  					} else {
  						phash = null;
  					}
  				}
  				return parents;
  			};
  		
  		if (! onlydir && self.api >= 2) {
  			(cwd !== this.root()) && opts.push(this.request({
  				data           : {cmd : 'parents', target : cwd},
  				preventDefault : true
  			}));
  			$.each(exParents(), function(i, data) {
  				opts.push(self.request({
  					data           : {cmd : data.cmd, target : data.target},
  					preventDefault : true
  				}));
  			});
  		}
  		$.when.apply($, opts)
  		.fail(function(error, xhr) {
  			if (! polling || $.inArray('errOpen', error) !== -1) {
  				dfrd.reject(error);
  				error && self.request({
  					data   : {cmd : 'open', target : (self.lastDir('') || self.root()), tree : 1, init : 1},
  					notify : {type : 'open', cnt : 1, hideCnt : true}
  				});
  			} else {
  				dfrd.reject((error && xhr.status != 0)? error : void 0);
  			}
  		})
  		.done(function(odata) {
  			var pdata, argLen, i;
  			
  			if (odata.cwd.compare) {
  				if (comp === odata.cwd.compare) {
  					return dfrd.reject();
  				}
  			}
  			
  			// for 2nd and more requests
  			pdata = {tree : []};
  			
  			// results marge of 2nd and more requests
  			argLen = arguments.length;
  			if (argLen > 1) {
  				for(i = 1; i < argLen; i++) {
  					if (arguments[i].tree && arguments[i].tree.length) {
  						pdata.tree.push.apply(pdata.tree, arguments[i].tree);
  					}
  				}
  			}
  			
  			if (self.api < 2.1) {
  				if (! pdata.tree) {
  					pdata.tree = [];
  				}
  				pdata.tree.push(odata.cwd);
  			}
  			
  			// data normalize
  			odata = self.normalize(odata);
  			if (!self.validResponse('open', odata)) {
  				return dfrd.reject((odata.norError || 'errResponse'));
  			}
  			pdata = self.normalize(pdata);
  			if (!self.validResponse('tree', pdata)) {
  				return dfrd.reject((pdata.norError || 'errResponse'));
  			}
  			
  			var diff = self.diff(odata.files.concat(pdata && pdata.tree ? pdata.tree : []), onlydir);
  
  			diff.added.push(odata.cwd);
  			
  			self.updateCache(diff);
  			
  			// trigger events
  			diff.removed.length && self.remove(diff);
  			diff.added.length   && self.add(diff);
  			diff.changed.length && self.change(diff);
  			return dfrd.resolve(diff);
  		})
  		.always(function() {
  			self.autoSync();
  		});
  		
  		return dfrd;
  	};
  	
  	this.upload = function(files) {
  		return this.transport.upload(files, this);
  	};
  	
  	/**
  	 * Arrays that has to unbind events
  	 * 
  	 * @type Object
  	 */
  	this.toUnbindEvents = {};
  	
  	/**
  	 * Attach listener to events
  	 * To bind to multiply events at once, separate events names by space
  	 * 
  	 * @param  String  event(s) name(s)
  	 * @param  Object  event handler
  	 * @param  Boolean priority first
  	 * @return elFinder
  	 */
  	this.bind = function(event, callback, priorityFirst) {
  		var i, len;
  		
  		if (typeof(callback) == 'function') {
  			event = ('' + event).toLowerCase().replace(/^\s+|\s+$/g, '').split(/\s+/);
  			
  			len = event.length;
  			for (i = 0; i < len; i++) {
  				if (listeners[event[i]] === void(0)) {
  					listeners[event[i]] = [];
  				}
  				listeners[event[i]][priorityFirst? 'unshift' : 'push'](callback);
  			}
  		}
  		return this;
  	};
  	
  	/**
  	 * Remove event listener if exists
  	 * To un-bind to multiply events at once, separate events names by space
  	 *
  	 * @param  String    event(s) name(s)
  	 * @param  Function  callback
  	 * @return elFinder
  	 */
  	this.unbind = function(event, callback) {
  		var i, len, l, ci;
  		
  		event = ('' + event).toLowerCase().split(/\s+/);
  		
  		len = event.length;
  		for (i = 0; i < len; i++) {
  			if (l = listeners[event[i]]) {
  				ci = $.inArray(callback, l);
  				ci > -1 && l.splice(ci, 1);
  			}
  		}
  		
  		callback = null;
  		return this;
  	};
  	
  	/**
  	 * Fire event - send notification to all event listeners
  	 * In the callback `this` becames an event object
  	 *
  	 * @param  String   event type
  	 * @param  Object   data to send across event
  	 * @param  Boolean  allow modify data (call by reference of data) default: true
  	 * @return elFinder
  	 */
  	this.trigger = function(evType, data, allowModify) {
  		var type      = evType.toLowerCase(),
  			isopen    = (type === 'open'),
  			dataIsObj = (typeof data === 'object'),
  			handlers  = listeners[type] || [], i, l, jst, event;
  		
  		this.debug('event-'+type, data);
  		
  		if (! dataIsObj || typeof allowModify === 'undefined') {
  			allowModify = true;
  		}
  		if (l = handlers.length) {
  			event = $.Event(type);
  			if (allowModify) {
  				event.data = data;
  			}
  
  			for (i = 0; i < l; i++) {
  				if (! handlers[i]) {
  					// probably un-binded this handler
  					continue;
  				}
  				// set `event.data` only callback has argument
  				if (handlers[i].length) {
  					if (!allowModify) {
  						// to avoid data modifications. remember about "sharing" passing arguments in js :) 
  						if (typeof jst === 'undefined') {
  							try {
  								jst = JSON.stringify(data);
  							} catch(e) {
  								jst = false;
  							}
  						}
  						event.data = jst? JSON.parse(jst) : data;
  					}
  				}
  
  				try {
  					if (handlers[i].call(event, event, this) === false 
  					|| event.isDefaultPrevented()) {
  						this.debug('event-stoped', event.type);
  						break;
  					}
  				} catch (ex) {
  					window.console && window.console.log && window.console.log(ex);
  				}
  				
  			}
  			
  			if (this.toUnbindEvents[type] && this.toUnbindEvents[type].length) {
  				$.each(this.toUnbindEvents[type], function(i, v) {
  					self.unbind(v.type, v.callback);
  				});
  				delete this.toUnbindEvents[type];
  			}
  		}
  		return this;
  	};
  	
  	/**
  	 * Get event listeners
  	 *
  	 * @param  String   event type
  	 * @return Array    listed event functions
  	 */
  	this.getListeners = function(event) {
  		return event? listeners[event.toLowerCase()] : listeners;
  	};
  	
  	/**
  	 * Bind keybord shortcut to keydown event
  	 *
  	 * @example
  	 *    elfinder.shortcut({ 
  	 *       pattern : 'ctrl+a', 
  	 *       description : 'Select all files', 
  	 *       callback : function(e) { ... }, 
  	 *       keypress : true|false (bind to keypress instead of keydown) 
  	 *    })
  	 *
  	 * @param  Object  shortcut config
  	 * @return elFinder
  	 */
  	this.shortcut = function(s) {
  		var patterns, pattern, code, i, parts;
  		
  		if (this.options.allowShortcuts && s.pattern && $.isFunction(s.callback)) {
  			patterns = s.pattern.toUpperCase().split(/\s+/);
  			
  			for (i= 0; i < patterns.length; i++) {
  				pattern = patterns[i];
  				parts   = pattern.split('+');
  				code    = (code = parts.pop()).length == 1 
  					? code > 0 ? code : code.charCodeAt(0) 
  					: (code > 0 ? code : $.ui.keyCode[code]);
  
  				if (code && !shortcuts[pattern]) {
  					shortcuts[pattern] = {
  						keyCode     : code,
  						altKey      : $.inArray('ALT', parts)   != -1,
  						ctrlKey     : $.inArray('CTRL', parts)  != -1,
  						shiftKey    : $.inArray('SHIFT', parts) != -1,
  						type        : s.type || 'keydown',
  						callback    : s.callback,
  						description : s.description,
  						pattern     : pattern
  					};
  				}
  			}
  		}
  		return this;
  	};
  	
  	/**
  	 * Registered shortcuts
  	 *
  	 * @type Object
  	 **/
  	this.shortcuts = function() {
  		var ret = [];
  		
  		$.each(shortcuts, function(i, s) {
  			ret.push([s.pattern, self.i18n(s.description)]);
  		});
  		return ret;
  	};
  	
  	/**
  	 * Get/set clipboard content.
  	 * Return new clipboard content.
  	 *
  	 * @example
  	 *   this.clipboard([]) - clean clipboard
  	 *   this.clipboard([{...}, {...}], true) - put 2 files in clipboard and mark it as cutted
  	 * 
  	 * @param  Array    new files hashes
  	 * @param  Boolean  cut files?
  	 * @return Array
  	 */
  	this.clipboard = function(hashes, cut) {
  		var map = function() { return $.map(clipboard, function(f) { return f.hash; }); };
  
  		if (hashes !== void(0)) {
  			clipboard.length && this.trigger('unlockfiles', {files : map()});
  			remember = {};
  			
  			clipboard = $.map(hashes||[], function(hash) {
  				var file = files[hash];
  				if (file) {
  					
  					remember[hash] = true;
  					
  					return {
  						hash   : hash,
  						phash  : file.phash,
  						name   : file.name,
  						mime   : file.mime,
  						read   : file.read,
  						locked : file.locked,
  						cut    : !!cut
  					};
  				}
  				return null;
  			});
  			this.trigger('changeclipboard', {clipboard : clipboard.slice(0, clipboard.length)});
  			cut && this.trigger('lockfiles', {files : map()});
  		}
  
  		// return copy of clipboard instead of refrence
  		return clipboard.slice(0, clipboard.length);
  	};
  	
  	/**
  	 * Return true if command enabled
  	 * 
  	 * @param  String       command name
  	 * @param  String|void  hash for check of own volume's disabled cmds
  	 * @return Boolean
  	 */
  	this.isCommandEnabled = function(name, dstHash) {
  		var disabled, cmd,
  			cvid = self.cwd().volumeid || '';
  		
  		// In serach results use selected item hash to check
  		if (!dstHash && self.searchStatus.state > 1 && self.selected().length) {
  			dstHash = self.selected()[0];
  		}
  		if (dstHash && (! cvid || dstHash.indexOf(cvid) !== 0)) {
  			disabled = self.option('disabledFlip', dstHash);
  			//if (! disabled) {
  			//	disabled = {};
  			//}
  		} else {
  			disabled = cwdOptions.disabledFlip/* || {}*/;
  		}
  		cmd = this._commands[name];
  		return cmd ? (cmd.alwaysEnabled || !disabled[name]) : false;
  	};
  	
  	/**
  	 * Exec command and return result;
  	 *
  	 * @param  String         command name
  	 * @param  String|Array   usualy files hashes
  	 * @param  String|Array   command options
  	 * @param  String|void    hash for enabled check of own volume's disabled cmds
  	 * @return $.Deferred
  	 */		
  	this.exec = function(cmd, files, opts, dstHash) {
  		var dfrd, resType;
  		
  		// apply commandMap for keyboard shortcut
  		if (!dstHash && this.commandMap[cmd] && this.commandMap[cmd] !== 'hidden') {
  			cmd = this.commandMap[cmd];
  		}
  
  		if (cmd === 'open') {
  			if (this.searchStatus.state || this.searchStatus.ininc) {
  				this.trigger('searchend', { noupdate: true });
  			}
  			this.autoSync('stop');
  		}
  		if (!dstHash && files) {
  			if ($.isArray(files)) {
  				if (files.length) {
  					dstHash = files[0];
  				}
  			} else {
  				dstHash = files;
  			}
  		}
  		dfrd = this._commands[cmd] && this.isCommandEnabled(cmd, dstHash) 
  			? this._commands[cmd].exec(files, opts) 
  			: $.Deferred().reject('No such command');
  		
  		resType = typeof dfrd;
  		if (!(resType === 'object' && dfrd.promise)) {
  			self.debug('warning', '"cmd.exec()" should be returned "$.Deferred" but cmd "' + cmd + '" returned "' + resType + '"');
  			dfrd = $.Deferred().resolve();
  		}
  		
  		this.trigger('exec', { dfrd : dfrd, cmd : cmd, files : files, opts : opts, dstHash : dstHash });
  		return dfrd;
  	};
  	
  	/**
  	 * Create and return dialog.
  	 *
  	 * @param  String|DOMElement  dialog content
  	 * @param  Object             dialog options
  	 * @return jQuery
  	 */
  	this.dialog = function(content, options) {
  		var dialog = $('<div/>').append(content).appendTo(node).elfinderdialog(options, this),
  			dnode  = dialog.closest('.ui-dialog'),
  			resize = function(){
  				! dialog.data('draged') && dialog.is(':visible') && dialog.elfinderdialog('posInit');
  			};
  		if (dnode.length) {
  			self.bind('resize', resize);
  			dnode.on('remove', function() {
  				self.unbind('resize', resize);
  			});
  		}
  		return dialog;
  	};
  	
  	/**
  	 * Create and return toast.
  	 *
  	 * @param  Object  toast options - see ui/toast.js
  	 * @return jQuery
  	 */
  	this.toast = function(options) {
  		return $('<div class="ui-front"/>').appendTo(this.ui.toast).elfindertoast(options || {}, this);
  	};
  	
  	/**
  	 * Return UI widget or node
  	 *
  	 * @param  String  ui name
  	 * @return jQuery
  	 */
  	this.getUI = function(ui) {
  		return this.ui[ui] || (ui? $() : node);
  	};
  	
  	/**
  	 * Return elFinder.command instance or instances array
  	 *
  	 * @param  String  command name
  	 * @return Object | Array
  	 */
  	this.getCommand = function(name) {
  		return name === void(0) ? this._commands : this._commands[name];
  	};
  	
  	/**
  	 * Resize elfinder node
  	 * 
  	 * @param  String|Number  width
  	 * @param  String|Number  height
  	 * @return void
  	 */
  	this.resize = function(w, h) {
  		var getMargin = function() {
  				var m = node.outerHeight(true) - node.innerHeight(),
  					p = node;
  				
  				while(p.get(0) !== heightBase.get(0)) {
  					p = p.parent();
  					m += p.outerHeight(true) - p.innerHeight();
  					if (! p.parent().length) {
  						// reached the document
  						break;
  					}
  				}
  				return m;
  			},
  			fit = ! node.hasClass('ui-resizable'),
  			prv = node.data('resizeSize') || {w: 0, h: 0},
  			mt, size = {};
  
  		if (heightBase && heightBase.data('resizeTm')) {
  			clearTimeout(heightBase.data('resizeTm'));
  		}
  		
  		if (typeof h === 'string') {
  			if (mt = h.match(/^([0-9.]+)%$/)) {
  				// setup heightBase
  				if (! heightBase || ! heightBase.length) {
  					heightBase = $(window);
  				}
  				if (! heightBase.data('marginToMyNode')) {
  					heightBase.data('marginToMyNode', getMargin());
  				}
  				if (! heightBase.data('fitToBaseFunc')) {
  					heightBase.data('fitToBaseFunc', function(e) {
  						var tm = heightBase.data('resizeTm');
  						e.preventDefault();
  						e.stopPropagation();
  						tm && cancelAnimationFrame(tm);
  						if (! node.hasClass('elfinder-fullscreen') && (!self.UA.Mobile || heightBase.data('rotated') !== self.UA.Rotated)) {
  							heightBase.data('rotated', self.UA.Rotated);
  							heightBase.data('resizeTm', requestAnimationFrame(function() {
  								self.restoreSize();
  							}));
  						}
  					});
  				}
  				if (typeof heightBase.data('rotated') === 'undefined') {
  					heightBase.data('rotated', self.UA.Rotated);
  				}
  				h = heightBase.height() * (mt[1] / 100) - heightBase.data('marginToMyNode');
  				
  				heightBase.off('resize.' + self.namespace, heightBase.data('fitToBaseFunc'));
  				fit && heightBase.on('resize.' + self.namespace, heightBase.data('fitToBaseFunc'));
  			}
  		}
  		
  		node.css({ width : w, height : parseInt(h) });
  		size.w = Math.round(node.width());
  		size.h = Math.round(node.height());
  		node.data('resizeSize', size);
  		if (size.w !== prv.w || size.h !== prv.h) {
  			node.trigger('resize');
  			this.trigger('resize', {width : size.w, height : size.h});
  		}
  	};
  	
  	/**
  	 * Restore elfinder node size
  	 * 
  	 * @return elFinder
  	 */
  	this.restoreSize = function() {
  		this.resize(width, height);
  	};
  	
  	this.show = function() {
  		node.show();
  		this.enable().trigger('show');
  	};
  	
  	this.hide = function() {
  		if (this.options.enableAlways) {
  			prevEnabled = enabled;
  			enabled = false;
  		}
  		this.disable().trigger('hide');
  		node.hide();
  	};
  	
  	/**
  	 * Lazy execution function
  	 * 
  	 * @param  Object  function
  	 * @param  Number  delay
  	 * @param  Object  options
  	 * @return Object  jQuery.Deferred
  	 */
  	this.lazy = function(func, delay, opts) {
  		var busy = function(state) {
  				var cnt = node.data('lazycnt'),
  					repaint;
  				
  				if (state) {
  					repaint = node.data('lazyrepaint')? false : opts.repaint;
  					if (! cnt) {
  						node.data('lazycnt', 1)
  							.addClass('elfinder-processing');
  					} else {
  						node.data('lazycnt', ++cnt);
  					}
  					if (repaint) {
  						node.data('lazyrepaint', true).css('display'); // force repaint
  					}
  				} else {
  					if (cnt && cnt > 1) {
  						node.data('lazycnt', --cnt);
  					} else {
  						repaint = node.data('lazyrepaint');
  						node.data('lazycnt', 0)
  							.removeData('lazyrepaint')
  							.removeClass('elfinder-processing');
  						repaint && node.css('display'); // force repaint;
  						self.trigger('lazydone');
  					}
  				}
  			},
  			dfd  = $.Deferred(),
  			callFunc = function() {
  				dfd.resolve(func.call(dfd));
  				busy(false);
  			};
  		
  		delay = delay || 0;
  		opts = opts || {};
  		busy(true);
  		
  		if (delay) {
  			setTimeout(callFunc, delay);
  		} else {
  			requestAnimationFrame(callFunc);
  		}
  		
  		return dfd;
  	};
  	
  	/**
  	 * Destroy this elFinder instance
  	 *
  	 * @return void
  	 **/
  	this.destroy = function() {
  		if (node && node[0].elfinder) {
  			node.hasClass('elfinder-fullscreen') && self.toggleFullscreen(node);
  			this.options.syncStart = false;
  			this.autoSync('forcestop');
  			this.trigger('destroy').disable();
  			clipboard = [];
  			selected = [];
  			listeners = {};
  			shortcuts = {};
  			$(window).off('.' + namespace);
  			$(document).off('.' + namespace);
  			self.trigger = function(){};
  			$(beeper).remove();
  			node.off()
  				.removeData()
  				.empty()
  				.append(prevContent.contents())
  				.attr('class', prevContent.attr('class'))
  				.attr('style', prevContent.attr('style'));
  			delete node[0].elfinder;
  			// restore kept events
  			$.each(prevEvents, function(n, arr) {
  				$.each(arr, function(i, o) {
  					node.on(o.type + (o.namespace? '.'+o.namespace : ''), o.selector, o.handler);
  				});
  			});
  		}
  	};
  	
  	/**
  	 * Start or stop auto sync
  	 * 
  	 * @param  String|Bool  stop
  	 * @return void
  	 */
  	this.autoSync = function(mode) {
  		var sync;
  		if (self.options.sync >= 1000) {
  			if (syncInterval) {
  				clearTimeout(syncInterval);
  				syncInterval = null;
  				self.trigger('autosync', {action : 'stop'});
  			}
  			
  			if (mode === 'stop') {
  				++autoSyncStop;
  			} else {
  				autoSyncStop = Math.max(0, --autoSyncStop);
  			}
  			
  			if (autoSyncStop || mode === 'forcestop' || ! self.options.syncStart) {
  				return;
  			} 
  			
  			// run interval sync
  			sync = function(start){
  				var timeout;
  				if (cwdOptions.syncMinMs && (start || syncInterval)) {
  					start && self.trigger('autosync', {action : 'start'});
  					timeout = Math.max(self.options.sync, cwdOptions.syncMinMs);
  					syncInterval && clearTimeout(syncInterval);
  					syncInterval = setTimeout(function() {
  						var dosync = true, hash = cwd, cts;
  						if (cwdOptions.syncChkAsTs && files[hash] && (cts = files[hash].ts)) {
  							self.request({
  								data           : {cmd : 'info', targets : [hash], compare : cts, reload : 1},
  								preventDefault : true
  							})
  							.done(function(data){
  								var ts;
  								dosync = true;
  								if (data.compare) {
  									ts = data.compare;
  									if (ts == cts) {
  										dosync = false;
  									}
  								}
  								if (dosync) {
  									self.sync(hash).always(function(){
  										if (ts) {
  											// update ts for cache clear etc.
  											files[hash].ts = ts;
  										}
  										sync();
  									});
  								} else {
  									sync();
  								}
  							})
  							.fail(function(error, xhr){
  								if (error && xhr.status != 0) {
  									self.error(error);
  									if ($.inArray('errOpen', error) !== -1) {
  										self.request({
  											data   : {cmd : 'open', target : (self.lastDir('') || self.root()), tree : 1, init : 1},
  											notify : {type : 'open', cnt : 1, hideCnt : true}
  										});
  									}
  								} else {
  									syncInterval = setTimeout(function() {
  										sync();
  									}, timeout);
  								}
  							});
  						} else {
  							self.sync(cwd, true).always(function(){
  								sync();
  							});
  						}
  					}, timeout);
  				}
  			};
  			sync(true);
  		}
  	};
  	
  	/**
  	 * Return bool is inside work zone of specific point
  	 * 
  	 * @param  Number event.pageX
  	 * @param  Number event.pageY
  	 * @return Bool
  	 */
  	this.insideWorkzone = function(x, y, margin) {
  		var rectangle = this.getUI('workzone').data('rectangle');
  		
  		margin = margin || 1;
  		if (x < rectangle.left + margin
  		|| x > rectangle.left + rectangle.width + margin
  		|| y < rectangle.top + margin
  		|| y > rectangle.top + rectangle.height + margin) {
  			return false;
  		}
  		return true;
  	};
  	
  	/**
  	 * Target ui node move to last of children of elFinder node fot to show front
  	 * 
  	 * @param  Object  target    Target jQuery node object
  	 */
  	this.toFront = function(target) {
  		var nodes = node.children('.ui-front').removeClass('elfinder-frontmost'),
  			lastnode = nodes.last();
  		nodes.css('z-index', '');
  		$(target).addClass('ui-front elfinder-frontmost').css('z-index', lastnode.css('z-index') + 1);
  	};
  	
  	/**
  	 * Remove class 'elfinder-frontmost' and hide() to target ui node
  	 *
  	 * @param      Object   target  Target jQuery node object
  	 * @param      Boolean  nohide  Do not hide
  	 */
  	this.toHide =function(target, nohide) {
  		var tgt = $(target),
  			last;
  
  		!nohide && tgt.hide();
  		if (tgt.hasClass('elfinder-frontmost')) {
  			tgt.removeClass('elfinder-frontmost');
  			last = node.children('.ui-front:visible:not(.elfinder-frontmost)').last();
  			if (last.length) {
  				requestAnimationFrame(function() {
  					if (!node.children('.elfinder-frontmost:visible').length) {
  						self.toFront(last);
  						last.trigger('frontmost');
  					}
  				});
  			}
  		}
  	};
  
  	/**
  	 * Return css object for maximize
  	 * 
  	 * @return Object
  	 */
  	this.getMaximizeCss = function() {
  		return {
  			width   : '100%',
  			height  : '100%',
  			margin  : 0,
  			padding : 0,
  			top     : 0,
  			left    : 0,
  			display : 'block',
  			position: 'fixed',
  			zIndex  : Math.max(self.zIndex? (self.zIndex + 1) : 0 , 1000),
  			maxWidth : '',
  			maxHeight: ''
  		};
  	};
  	
  	// Closure for togglefullscreen
  	(function() {
  		// check is in iframe
  		if (inFrame && self.UA.Fullscreen) {
  			self.UA.Fullscreen = false;
  			if (parentIframe && typeof parentIframe.attr('allowfullscreen') !== 'undefined') {
  				self.UA.Fullscreen = true;
  			}
  		}
  
  		var orgStyle, bodyOvf, resizeTm, fullElm, exitFull, toFull,
  			cls = 'elfinder-fullscreen',
  			clsN = 'elfinder-fullscreen-native',
  			checkDialog = function() {
  				var t = 0,
  					l = 0;
  				$.each(node.children('.ui-dialog,.ui-draggable'), function(i, d) {
  					var $d = $(d),
  						pos = $d.position();
  					
  					if (pos.top < 0) {
  						$d.css('top', t);
  						t += 20;
  					}
  					if (pos.left < 0) {
  						$d.css('left', l);
  						l += 20;
  					}
  				});
  			},
  			funcObj = self.UA.Fullscreen? {
  				// native full screen mode
  				
  				fullElm: function() {
  					return document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement || null;
  				},
  				
  				exitFull: function() {
  					if (document.exitFullscreen) {
  						return document.exitFullscreen();
  					} else if (document.webkitExitFullscreen) {
  						return document.webkitExitFullscreen();
  					} else if (document.mozCancelFullScreen) {
  						return document.mozCancelFullScreen();
  					} else if (document.msExitFullscreen) {
  						return document.msExitFullscreen();
  					}
  				},
  				
  				toFull: function(elem) {
  					if (elem.requestFullscreen) {
  						return elem.requestFullscreen();
  					} else if (elem.webkitRequestFullscreen) {
  						return elem.webkitRequestFullscreen();
  					} else if (elem.mozRequestFullScreen) {
  						return elem.mozRequestFullScreen();
  					} else if (elem.msRequestFullscreen) {
  						return elem.msRequestFullscreen();
  					}
  					return false;
  				}
  			} : {
  				// node element maximize mode
  				
  				fullElm: function() {
  					var full;
  					if (node.hasClass(cls)) {
  						return node.get(0);
  					} else {
  						full = node.find('.' + cls);
  						if (full.length) {
  							return full.get(0);
  						}
  					}
  					return null;
  				},
  				
  				exitFull: function() {
  					var elm;
  					
  					$(window).off('resize.' + namespace, resize);
  					if (bodyOvf !== void(0)) {
  						$('body').css('overflow', bodyOvf);
  					}
  					bodyOvf = void(0);
  					
  					if (orgStyle) {
  						elm = orgStyle.elm;
  						restoreStyle(elm);
  						$(elm).trigger('resize', {fullscreen: 'off'});
  					}
  					
  					$(window).trigger('resize');
  				},
  				
  				toFull: function(elem) {
  					bodyOvf = $('body').css('overflow') || '';
  					$('body').css('overflow', 'hidden');
  					
  					$(elem).css(self.getMaximizeCss())
  						.addClass(cls)
  						.trigger('resize', {fullscreen: 'on'});
  					
  					checkDialog();
  					
  					$(window).on('resize.' + namespace, resize).trigger('resize');
  					
  					return true;
  				}
  			},
  			restoreStyle = function(elem) {
  				if (orgStyle && orgStyle.elm == elem) {
  					$(elem).removeClass(cls + ' ' + clsN).attr('style', orgStyle.style);
  					orgStyle = null;
  				}
  			},
  			resize = function(e) {
  				var elm;
  				if (e.target === window) {
  					resizeTm && cancelAnimationFrame(resizeTm);
  					resizeTm = requestAnimationFrame(function() {
  						if (elm = funcObj.fullElm()) {
  							$(elm).trigger('resize', {fullscreen: 'on'});
  						}
  					});
  				}
  			};
  		
  		$(document).on('fullscreenchange.' + namespace + ' webkitfullscreenchange.' + namespace + ' mozfullscreenchange.' + namespace + ' MSFullscreenChange.' + namespace, function(e){
  			if (self.UA.Fullscreen) {
  				var elm = funcObj.fullElm(),
  					win = $(window);
  				
  				resizeTm && cancelAnimationFrame(resizeTm);
  				if (elm === null) {
  					win.off('resize.' + namespace, resize);
  					if (orgStyle) {
  						elm = orgStyle.elm;
  						restoreStyle(elm);
  						$(elm).trigger('resize', {fullscreen: 'off'});
  					}
  				} else {
  					$(elm).addClass(cls + ' ' + clsN)
  						.attr('style', 'width:100%; height:100%; margin:0; padding:0;')
  						.trigger('resize', {fullscreen: 'on'});
  					win.on('resize.' + namespace, resize);
  					checkDialog();
  				}
  				win.trigger('resize');
  			}
  		});
  		
  		/**
  		 * Toggle Full Scrren Mode
  		 * 
  		 * @param  Object target
  		 * @param  Bool   full
  		 * @return Object | Null  DOM node object of current full scrren
  		 */
  		self.toggleFullscreen = function(target, full) {
  			var elm = $(target).get(0),
  				curElm = null;
  			
  			curElm = funcObj.fullElm();
  			if (curElm) {
  				if (curElm == elm) {
  					if (full === true) {
  						return curElm;
  					}
  				} else {
  					if (full === false) {
  						return curElm;
  					}
  				}
  				funcObj.exitFull();
  				return null;
  			} else {
  				if (full === false) {
  					return null;
  				}
  			}
  			
  			orgStyle = {elm: elm, style: $(elm).attr('style')};
  			if (funcObj.toFull(elm) !== false) {
  				return elm;
  			} else {
  				orgStyle = null;
  				return null;
  			}
  		};
  	})();
  	
  	// Closure for toggleMaximize
  	(function(){
  		var cls = 'elfinder-maximized',
  		resizeTm,
  		resize = function(e) {
  			if (e.target === window && e.data && e.data.elm) {
  				var elm = e.data.elm;
  				resizeTm && cancelAnimationFrame(resizeTm);
  				resizeTm = requestAnimationFrame(function() {
  					elm.trigger('resize', {maximize: 'on'});
  				});
  			}
  		},
  		exitMax = function(elm) {
  			$(window).off('resize.' + namespace, resize);
  			$('body').css('overflow', elm.data('bodyOvf'));
  			elm.removeClass(cls)
  				.attr('style', elm.data('orgStyle'))
  				.removeData('bodyOvf')
  				.removeData('orgStyle');
  			elm.trigger('resize', {maximize: 'off'});
  		},
  		toMax = function(elm) {
  			elm.data('bodyOvf', $('body').css('overflow') || '')
  				.data('orgStyle', elm.attr('style'))
  				.addClass(cls)
  				.css(self.getMaximizeCss());
  			$('body').css('overflow', 'hidden');
  			$(window).on('resize.' + namespace, {elm: elm}, resize);
  			elm.trigger('resize', {maximize: 'on'});
  		};
  		
  		/**
  		 * Toggle Maximize target node
  		 * 
  		 * @param  Object target
  		 * @param  Bool   max
  		 * @return void
  		 */
  		self.toggleMaximize = function(target, max) {
  			var elm = $(target),
  				maximized = elm.hasClass(cls);
  			
  			if (maximized) {
  				if (max === true) {
  					return;
  				}
  				exitMax(elm);
  			} else {
  				if (max === false) {
  					return;
  				}
  				toMax(elm);
  			}
  		};
  	})();
  	
  	/*************  init stuffs  ****************/
  	Object.assign($.ui.keyCode, {
  		'F1' : 112,
  		'F2' : 113,
  		'F3' : 114,
  		'F4' : 115,
  		'F5' : 116,
  		'F6' : 117,
  		'F7' : 118,
  		'F8' : 119,
  		'F9' : 120,
  		'F10' : 121,
  		'F11' : 122,
  		'F12' : 123,
  		'DIG0' : 48,
  		'DIG1' : 49,
  		'DIG2' : 50,
  		'DIG3' : 51,
  		'DIG4' : 52,
  		'DIG5' : 53,
  		'DIG6' : 54,
  		'DIG7' : 55,
  		'DIG8' : 56,
  		'DIG9' : 57,
  		'NUM0' : 96,
  		'NUM1' : 97,
  		'NUM2' : 98,
  		'NUM3' : 99,
  		'NUM4' : 100,
  		'NUM5' : 101,
  		'NUM6' : 102,
  		'NUM7' : 103,
  		'NUM8' : 104,
  		'NUM9' : 105,
  		'CONTEXTMENU' : 93
  	});
  	
  	this.dragUpload = false;
  	this.xhrUpload  = (typeof XMLHttpRequestUpload != 'undefined' || typeof XMLHttpRequestEventTarget != 'undefined') && typeof File != 'undefined' && typeof FormData != 'undefined';
  	
  	// configure transport object
  	this.transport = {};
  
  	if (typeof(this.options.transport) == 'object') {
  		this.transport = this.options.transport;
  		if (typeof(this.transport.init) == 'function') {
  			this.transport.init(this);
  		}
  	}
  	
  	if (typeof(this.transport.send) != 'function') {
  		this.transport.send = function(opts) { return $.ajax(opts); };
  	}
  	
  	if (this.transport.upload == 'iframe') {
  		this.transport.upload = $.proxy(this.uploads.iframe, this);
  	} else if (typeof(this.transport.upload) == 'function') {
  		this.dragUpload = !!this.options.dragUploadAllow;
  	} else if (this.xhrUpload && !!this.options.dragUploadAllow) {
  		this.transport.upload = $.proxy(this.uploads.xhr, this);
  		this.dragUpload = true;
  	} else {
  		this.transport.upload = $.proxy(this.uploads.iframe, this);
  	}
  
  	/**
  	 * Decoding 'raw' string converted to unicode
  	 * 
  	 * @param  String str
  	 * @return String
  	 */
  	this.decodeRawString = function(str) {
  		var charCodes = function(str) {
  			var i, len, arr;
  			for (i=0,len=str.length,arr=[]; i<len; i++) {
  				arr.push(str.charCodeAt(i));
  			}
  			return arr;
  		},
  		scalarValues = function(arr) {
  			var scalars = [], i, len, c;
  			if (typeof arr === 'string') {arr = charCodes(arr);}
  			for (i=0,len=arr.length; c=arr[i],i<len; i++) {
  				if (c >= 0xd800 && c <= 0xdbff) {
  					scalars.push((c & 1023) + 64 << 10 | arr[++i] & 1023);
  				} else {
  					scalars.push(c);
  				}
  			}
  			return scalars;
  		},
  		decodeUTF8 = function(arr) {
  			var i, len, c, str, char = String.fromCharCode;
  			for (i=0,len=arr.length,str=""; c=arr[i],i<len; i++) {
  				if (c <= 0x7f) {
  					str += char(c);
  				} else if (c <= 0xdf && c >= 0xc2) {
  					str += char((c&31)<<6 | arr[++i]&63);
  				} else if (c <= 0xef && c >= 0xe0) {
  					str += char((c&15)<<12 | (arr[++i]&63)<<6 | arr[++i]&63);
  				} else if (c <= 0xf7 && c >= 0xf0) {
  					str += char(
  						0xd800 | ((c&7)<<8 | (arr[++i]&63)<<2 | arr[++i]>>>4&3) - 64,
  						0xdc00 | (arr[i++]&15)<<6 | arr[i]&63
  					);
  				} else {
  					str += char(0xfffd);
  				}
  			}
  			return str;
  		};
  		
  		return decodeUTF8(scalarValues(str));
  	};
  
  	/**
  	 * Gets target file contents by file.hash
  	 *
  	 * @param      String  hash          The hash
  	 * @param      String  responseType  'blob' or 'arraybuffer' (default)
  	 * @return     arraybuffer|blob  The contents.
  	 */
  	this.getContents = function(hash, responseType) {
  		var self = this,
  			dfd = $.Deferred(),
  			type = responseType || 'arraybuffer',
  			url, req;
  
  		dfd.fail(function() {
  			req && req.state() === 'pending' && req.reject();
  		});
  
  		url = self.openUrl(hash);
  		if (!self.isSameOrigin(url)) {
  			url = self.openUrl(hash, true);
  		}
  		req = self.request({
  			data    : {cmd : 'get'},
  			options : {
  				url: url,
  				type: 'get',
  				cache : true,
  				dataType : 'binary',
  				responseType : type,
  				processData: false
  			}
  		})
  		.fail(function() {
  			dfd.reject();
  		})
  		.done(function(data) {
  			dfd.resolve(data);
  		});
  
  		return dfd;
  	};
  
  	/**
  	 * Supported check hash algorisms
  	 * 
  	 * @type Array
  	 */
  	self.hashCheckers = [];
  
  	/**
  	 * Closure of getContentsHashes()
  	 */
  	(function(self) {
  		var hashLibs = {
  				check : true
  			},
  			md5Calc = function(arr) {
  				var spark = new hashLibs.SparkMD5.ArrayBuffer(),
  					job;
  
  				job = self.asyncJob(function(buf) {
  					spark.append(buf);
  				}, arr).done(function() {
  					job._md5 = spark.end();
  				});
  
  				return job;
  			},
  			shaCalc = function(arr, length) {
  				var sha, job;
  
  				try {
  					sha = new hashLibs.jsSHA('SHA' + (length.substr(0, 1) === '3'? length : ('-' + length)), 'ARRAYBUFFER');
  					job = self.asyncJob(function(buf) {
  						sha.update(buf);
  					}, arr).done(function() {
  						job._sha = sha.getHash('HEX');
  					});
  				} catch(e) {
  					job = $.Deferred.reject();
  				}
  
  				return job;
  			};
  
  		// make fm.hashCheckers
  		if (self.options.cdns.sparkmd5) {
  			self.hashCheckers.push('md5');
  		}
  		if (self.options.cdns.jssha) {
  			self.hashCheckers = self.hashCheckers.concat(['sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512', 'shake128', 'shake256']);
  		}
  
  		/**
  		 * Gets the contents hashes.
  		 *
  		 * @param      String  target      target file.hash
  		 * @param      Object  needHashes  need hash lib names
  		 * @return     Object  hashes with lib name as key
  		 */
  		self.getContentsHashes = function(target, needHashes) {
  			var dfd = $.Deferred(),
  				needs = self.arrayFlip(needHashes || ['md5'], true),
  				libs = [],
  				jobs = [],
  				res = {},
  				req;
  
  			dfd.fail(function() {
  				req && req.reject();
  			});
  
  			if (hashLibs.check) {
  
  				delete hashLibs.check;
  
  				// load SparkMD5
  				var libsmd5 = $.Deferred();
  				if (window.ArrayBuffer && self.options.cdns.sparkmd5) {
  					libs.push(libsmd5);
  					self.loadScript([self.options.cdns.sparkmd5],
  						function(res) { 
  							var SparkMD5 = res || window.SparkMD5;
  							window.SparkMD5 && delete window.SparkMD5;
  							libsmd5.resolve();
  							if (SparkMD5) {
  								hashLibs.SparkMD5 = SparkMD5;
  							}
  						},
  						{
  							tryRequire: true,
  							error: function() {
  								libsmd5.reject();
  							}
  						}
  					);
  				}
  
  				// load jsSha
  				var libssha = $.Deferred();
  				if (window.ArrayBuffer && self.options.cdns.jssha) {
  					libs.push(libssha);
  					self.loadScript([self.options.cdns.jssha],
  						function(res) { 
  							var jsSHA = res || window.jsSHA;
  							window.jsSHA && delete window.jsSHA;
  							libssha.resolve();
  							if (jsSHA) {
  								hashLibs.jsSHA = jsSHA;
  							}
  						},
  						{
  							tryRequire: true,
  							error: function() {
  								libssha.reject();
  							}
  						}
  					);
  				}
  			}
  			
  			$.when.apply(null, libs).always(function() {
  				if (Object.keys(hashLibs).length) {
  					req = self.getContents(target).done(function(arrayBuffer) {
  						var arr = (arrayBuffer instanceof ArrayBuffer && arrayBuffer.byteLength > 0)? self.sliceArrayBuffer(arrayBuffer, 1048576) : false,
  							i;
  
  						if (needs.md5 && hashLibs.SparkMD5) {
  							jobs.push(function() {
  								var job = md5Calc(arr).done(function() {
  									var f;
  									res.md5 = job._md5;
  									if (f = self.file(target)) {
  										f.md5 = job._md5;
  									}
  									dfd.notify(res);
  								});
  								dfd.fail(function() {
  									job.reject();
  								});
  								return job;
  							});
  						}
  						if (hashLibs.jsSHA) {
  							$.each(['1', '224', '256', '384', '512', '3-224', '3-256', '3-384', '3-512', 'ke128', 'ke256'], function(i, v) {
  								if (needs['sha' + v]) {
  									jobs.push(function() {
  										var job = shaCalc(arr, v).done(function() {
  											var f;
  											res['sha' + v] = job._sha;
  											if (f = self.file(target)) {
  												f['sha' + v] = job._sha;
  											}
  											dfd.notify(res);
  										});
  										return job;
  									});
  								}
  							});
  						}
  						if (jobs.length) {
  							self.sequence(jobs).always(function() {
  								dfd.resolve(res);
  							});
  						} else {
  							dfd.reject();
  						}
  					}).fail(function() {
  						dfd.reject();
  					});
  				} else {
  					dfd.reject();
  				}
  			});
  
  			return dfd;
  		};
  	})(this);
  
  	/**
  	 * Alias for this.trigger('error', {error : 'message'})
  	 *
  	 * @param  String  error message
  	 * @return elFinder
  	 **/
  	this.error = function() {
  		var arg = arguments[0],
  			opts = arguments[1] || null;
  		return arguments.length == 1 && typeof(arg) == 'function'
  			? self.bind('error', arg)
  			: (arg === true? this : self.trigger('error', {error : arg, opts : opts}));
  	};
  	
  	// create bind/trigger aliases for build-in events
  	$.each(events, function(i, name) {
  		self[name] = function() {
  			var arg = arguments[0];
  			return arguments.length == 1 && typeof(arg) == 'function'
  				? self.bind(name, arg)
  				: self.trigger(name, $.isPlainObject(arg) ? arg : {});
  		};
  	});
  
  	// bind core event handlers
  	this
  		.enable(function() {
  			if (!enabled && self.api && self.visible() && self.ui.overlay.is(':hidden') && ! node.children('.elfinder-dialog.' + self.res('class', 'editing') + ':visible').length) {
  				enabled = true;
  				document.activeElement && document.activeElement.blur();
  				node.removeClass('elfinder-disabled');
  			}
  		})
  		.disable(function() {
  			prevEnabled = enabled;
  			enabled = false;
  			node.addClass('elfinder-disabled');
  		})
  		.open(function() {
  			selected = [];
  		})
  		.select(function(e) {
  			var cnt = 0,
  				unselects = [];
  			selected = $.grep(e.data.selected || e.data.value|| [], function(hash) {
  				if (unselects.length || (self.maxTargets && ++cnt > self.maxTargets)) {
  					unselects.push(hash);
  					return false;
  				} else {
  					return files[hash] ? true : false;
  				}
  			});
  			if (unselects.length) {
  				self.trigger('unselectfiles', {files: unselects, inselect: true});
  				self.toast({mode: 'warning', msg: self.i18n(['errMaxTargets', self.maxTargets])});
  			}
  		})
  		.error(function(e) { 
  			var opts  = {
  					cssClass  : 'elfinder-dialog-error',
  					title     : self.i18n(self.i18n('error')),
  					resizable : false,
  					destroyOnClose : true,
  					buttons   : {}
  			};
  			
  			opts.buttons[self.i18n(self.i18n('btnClose'))] = function() { $(this).elfinderdialog('close'); };
  
  			if (e.data.opts && $.isPlainObject(e.data.opts)) {
  				Object.assign(opts, e.data.opts);
  			}
  
  			self.dialog('<span class="elfinder-dialog-icon elfinder-dialog-icon-error"/>'+self.i18n(e.data.error), opts);
  		})
  		.bind('tmb', function(e) {
  			$.each(e.data.images||[], function(hash, tmb) {
  				if (files[hash]) {
  					files[hash].tmb = tmb;
  				}
  			});
  		})
  		.bind('searchstart', function(e) {
  			Object.assign(self.searchStatus, e.data);
  			self.searchStatus.state = 1;
  		})
  		.bind('search', function(e) {
  			self.searchStatus.state = 2;
  		})
  		.bind('searchend', function() {
  			self.searchStatus.state = 0;
  			self.searchStatus.ininc = false;
  			self.searchStatus.mixed = false;
  		})
  
  		;
  
  	// We listen and emit a sound on delete according to option
  	if (true === this.options.sound) {
  		this.bind('playsound', function(e) {
  			var play  = beeper.canPlayType && beeper.canPlayType('audio/wav; codecs="1"'),
  				file = e.data && e.data.soundFile;
  
  			play && file && play != '' && play != 'no' && $(beeper).html('<source src="' + soundPath + file + '" type="audio/wav">')[0].play();
  		});
  	}
  
  	// bind external event handlers
  	$.each(this.options.handlers, function(event, callback) {
  		self.bind(event, callback);
  	});
  
  	/**
  	 * History object. Store visited folders
  	 *
  	 * @type Object
  	 **/
  	this.history = new this.history(this);
  	
  	/**
  	 * Root hashed
  	 * 
  	 * @type Object
  	 */
  	this.roots = {};
  	
  	/**
  	 * leaf roots
  	 * 
  	 * @type Object
  	 */
  	this.leafRoots = {};
  	
  	this.volumeExpires = {};
  
  	/**
  	 * Loaded commands
  	 *
  	 * @type Object
  	 **/
  	this._commands = {};
  	
  	if (!Array.isArray(this.options.commands)) {
  		this.options.commands = [];
  	}
  	
  	if ($.inArray('*', this.options.commands) !== -1) {
  		this.options.commands = Object.keys(this.commands);
  	}
  	
  	/**
  	 * UI command map of cwd volume ( That volume driver option `uiCmdMap` )
  	 *
  	 * @type Object
  	 **/
  	this.commandMap = {};
  	
  	/**
  	 * cwd options of each volume
  	 * key: volumeid
  	 * val: options object
  	 * 
  	 * @type Object
  	 */
  	this.volOptions = {};
  
  	/**
  	 * Has volOptions data
  	 * 
  	 * @type Boolean
  	 */
  	this.hasVolOptions = false;
  
  	/**
  	 * Hash of trash holders
  	 * key: trash folder hash
  	 * val: source volume hash
  	 * 
  	 * @type Object
  	 */
  	this.trashes = {};
  
  	/**
  	 * cwd options of each folder/file
  	 * key: hash
  	 * val: options object
  	 *
  	 * @type Object
  	 */
  	this.optionsByHashes = {};
  	
  	/**
  	 * UI Auto Hide Functions
  	 * Each auto hide function mast be call to `fm.trigger('uiautohide')` at end of process
  	 *
  	 * @type Array
  	 **/
  	this.uiAutoHide = [];
  	
  	// trigger `uiautohide`
  	this.one('open', function() {
  		if (self.uiAutoHide.length) {
  			setTimeout(function() {
  				self.trigger('uiautohide');
  			}, 500);
  		}
  	});
  	
  	// Auto Hide Functions sequential processing start
  	this.bind('uiautohide', function() {
  		if (self.uiAutoHide.length) {
  			self.uiAutoHide.shift()();
  		}
  	});
  
  	if (this.options.width) {
  		width = this.options.width;
  	}
  	
  	if (this.options.height) {
  		height = this.options.height;
  	}
  	
  	if (this.options.heightBase) {
  		heightBase = $(this.options.heightBase);
  	}
  	
  	if (this.options.soundPath) {
  		soundPath = this.options.soundPath.replace(/\/+$/, '') + '/';
  	} else {
  		soundPath = this.baseUrl + soundPath;
  	}
  	
  	self.one('opendone', function() {
  		var tm;
  		// attach events to document
  		$(document)
  			// disable elfinder on click outside elfinder
  			.on('click.'+namespace, function(e) { enabled && ! self.options.enableAlways && !$(e.target).closest(node).length && self.disable(); })
  			// exec shortcuts
  			.on(keydown+' '+keypress+' '+keyup+' '+mousedown, execShortcut);
  		
  		// attach events to window
  		self.options.useBrowserHistory && $(window)
  			.on('popstate.' + namespace, function(ev) {
  				var state = ev.originalEvent.state || {},
  					hasThash = state.thash? true : false,
  					dialog = node.find('.elfinder-frontmost:visible'),
  					input = node.find('.elfinder-navbar-dir,.elfinder-cwd-filename').find('input,textarea'),
  					onOpen, toast;
  				if (!hasThash) {
  					state = { thash: self.cwd().hash };
  					// scroll to elFinder node
  					$('html,body').animate({ scrollTop: node.offset().top });
  				}
  				if (dialog.length || input.length) {
  					history.pushState(state, null, location.pathname + location.search + '#elf_' + state.thash);
  					if (dialog.length) {
  						if (!dialog.hasClass(self.res('class', 'preventback'))) {
  							if (dialog.hasClass('elfinder-contextmenu')) {
  								$(document).trigger($.Event('keydown', { keyCode: $.ui.keyCode.ESCAPE, ctrlKey : false, shiftKey : false, altKey : false, metaKey : false }));
  							} else if (dialog.hasClass('elfinder-dialog')) {
  								dialog.elfinderdialog('close');
  							} else {
  								dialog.trigger('close');
  							}
  						}
  					} else {
  						input.trigger($.Event('keydown', { keyCode: $.ui.keyCode.ESCAPE, ctrlKey : false, shiftKey : false, altKey : false, metaKey : false }));
  					}
  				} else {
  					if (hasThash) {
  						!$.isEmptyObject(self.files()) && self.request({
  							data   : {cmd  : 'open', target : state.thash, onhistory : 1},
  							notify : {type : 'open', cnt : 1, hideCnt : true},
  							syncOnFail : true
  						});
  					} else {
  						onOpen = function() {
  							toast.trigger('click');
  						};
  						self.one('open', onOpen, true);
  						toast = self.toast({
  							msg: self.i18n('pressAgainToExit'),
  							onHidden: function() {
  								self.unbind('open', onOpen);
  								history.pushState(state, null, location.pathname + location.search + '#elf_' + state.thash);
  							}
  						});
  					}
  				}
  			});
  		
  		$(window).on('resize.' + namespace, function(e){
  			if (e.target === this) {
  				tm && cancelAnimationFrame(tm);
  				tm = requestAnimationFrame(function() {
  					var prv = node.data('resizeSize') || {w: 0, h: 0},
  						size = {w: Math.round(node.width()), h: Math.round(node.height())};
  					node.data('resizeSize', size);
  					if (size.w !== prv.w || size.h !== prv.h) {
  						node.trigger('resize');
  						self.trigger('resize', {width : size.w, height : size.h});
  					}
  				});
  			}
  		})
  		.on('beforeunload.' + namespace,function(e){
  			var msg, cnt;
  			if (node.is(':visible')) {
  				if (self.ui.notify.children().length && $.inArray('hasNotifyDialog', self.options.windowCloseConfirm) !== -1) {
  					msg = self.i18n('ntfsmth');
  				} else if (node.find('.'+self.res('class', 'editing')).length && $.inArray('editingFile', self.options.windowCloseConfirm) !== -1) {
  					msg = self.i18n('editingFile');
  				} else if ((cnt = Object.keys(self.selected()).length) && $.inArray('hasSelectedItem', self.options.windowCloseConfirm) !== -1) {
  					msg = self.i18n('hasSelected', ''+cnt);
  				} else if ((cnt = Object.keys(self.clipboard()).length) && $.inArray('hasClipboardData', self.options.windowCloseConfirm) !== -1) {
  					msg = self.i18n('hasClipboard', ''+cnt);
  				}
  				if (msg) {
  					e.returnValue = msg;
  					return msg;
  				}
  			}
  			self.trigger('unload');
  		});
  
  		// bind window onmessage for CORS
  		$(window).on('message.' + namespace, function(e){
  			var res = e.originalEvent || null,
  				obj, data;
  			if (res && self.uploadURL.indexOf(res.origin) === 0) {
  				try {
  					obj = JSON.parse(res.data);
  					data = obj.data || null;
  					if (data) {
  						if (data.error) {
  							if (obj.bind) {
  								self.trigger(obj.bind+'fail', data);
  							}
  							self.error(data.error);
  						} else {
  							data.warning && self.error(data.warning);
  							self.updateCache(data);
  							data.removed && data.removed.length && self.remove(data);
  							data.added   && data.added.length   && self.add(data);
  							data.changed && data.changed.length && self.change(data);
  							if (obj.bind) {
  								self.trigger(obj.bind, data);
  								self.trigger(obj.bind+'done');
  							}
  							data.sync && self.sync();
  						}
  					}
  				} catch (e) {
  					self.sync();
  				}
  			}
  		});
  
  		// elFinder enable always
  		if (self.options.enableAlways) {
  			$(window).on('focus.' + namespace, function(e){
  				(e.target === this) && self.enable();
  			});
  			if (inFrame) {
  				$(window.top).on('focus.' + namespace, function() {
  					if (self.enable() && (! parentIframe || parentIframe.is(':visible'))) {
  						requestAnimationFrame(function() {
  							$(window).trigger('focus');
  						});
  					}
  				});
  			}
  		} else if (inFrame) {
  			$(window).on('blur.' + namespace, function(e){
  				enabled && e.target === this && self.disable();
  			});
  		}
  
  		// return focus to the window on click (elFInder in the frame)
  		if (inFrame) {
  			node.on('click', function(e) {
  				$(window).trigger('focus');
  			});
  		}
  		
  		// elFinder to enable by mouse over
  		if (self.options.enableByMouseOver) {
  			node.on('mouseenter touchstart', function(e) {
  				(inFrame) && $(window).trigger('focus');
  				! self.enabled() && self.enable();
  			});
  		}
  	});
  
  	// store instance in node
  	node[0].elfinder = this;
  
  	// auto load language file
  	dfrdsBeforeBootup.push((function() {
  		var lang   = self.lang,
  			langJs = self.baseUrl + 'js/i18n/elfinder.' + lang + '.js',
  			dfd    = $.Deferred().done(function() {
  				if (self.i18[lang]) {
  					self.lang = lang;
  				}
  				self.trigger('i18load');
  				i18n = self.lang === 'en' 
  					? self.i18['en'] 
  					: $.extend(true, {}, self.i18['en'], self.i18[self.lang]);
  			});
  		
  		if (!self.i18[lang]) {
  			self.lang = 'en';
  			if (self.hasRequire) {
  				require([langJs], function() {
  					dfd.resolve();
  				}, function() {
  					dfd.resolve();
  				});
  			} else {
  				self.loadScript([langJs], function() {
  					dfd.resolve();
  				}, {
  					loadType: 'tag',
  					error : function() {
  						dfd.resolve();
  					}
  				});
  			}
  		} else {
  			dfd.resolve();
  		}
  		return dfd;
  	})());
  	
  	// elFinder boot up function
  	bootUp = function() {
  		var columnNames;
  
  		/**
  		 * i18 messages
  		 *
  		 * @type Object
  		 **/
  		self.messages = i18n.messages;
  		
  		// check jquery ui
  		if (!($.fn.selectable && $.fn.draggable && $.fn.droppable && $.fn.resizable && $.fn.slider)) {
  			return alert(self.i18n('errJqui'));
  		}
  		
  		// check node
  		if (!node.length) {
  			return alert(self.i18n('errNode'));
  		}
  		// check connector url
  		if (!self.options.url) {
  			return alert(self.i18n('errURL'));
  		}
  		
  		// column key/name map for fm.getColumnName()
  		columnNames = Object.assign({
  			name : self.i18n('name'),
  			perm : self.i18n('perms'),
  			date : self.i18n('modify'),
  			size : self.i18n('size'),
  			kind : self.i18n('kind'),
  			modestr : self.i18n('mode'),
  			modeoct : self.i18n('mode'),
  			modeboth : self.i18n('mode')
  		}, self.options.uiOptions.cwd.listView.columnsCustomName);
  
  		/**
  		 * Gets the column name of cwd list view
  		 *
  		 * @param      String  key     The key
  		 * @return     String  The column name.
  		 */
  		self.getColumnName = function(key) {
  			return columnNames[key] || self.i18n(key);
  		};
  
  		/**
  		 * Interface direction
  		 *
  		 * @type String
  		 * @default "ltr"
  		 **/
  		self.direction = i18n.direction;
  		
  		/**
  		 * Date/time format
  		 *
  		 * @type String
  		 * @default "m.d.Y"
  		 **/
  		self.dateFormat = self.options.dateFormat || i18n.dateFormat;
  		
  		/**
  		 * Date format like "Yesterday 10:20:12"
  		 *
  		 * @type String
  		 * @default "{day} {time}"
  		 **/
  		self.fancyFormat = self.options.fancyDateFormat || i18n.fancyDateFormat;
  		
  		/**
  		 * Date format for if upload file has not original unique name
  		 * e.g. Clipboard image data, Image data taken with iOS
  		 *
  		 * @type String
  		 * @default "ymd-His"
  		 **/
  		self.nonameDateFormat = (self.options.nonameDateFormat || i18n.nonameDateFormat).replace(/[\/\\]/g, '_');
  
  		/**
  		 * Css classes 
  		 *
  		 * @type String
  		 **/
  		self.cssClass = 'ui-helper-reset ui-helper-clearfix ui-widget ui-widget-content ui-corner-all elfinder elfinder-'
  				+(self.direction == 'rtl' ? 'rtl' : 'ltr')
  				+(self.UA.Touch? (' elfinder-touch' + (self.options.resizable ? ' touch-punch' : '')) : '')
  				+(self.UA.Mobile? ' elfinder-mobile' : '')
  				+(self.UA.iOS? ' elfinder-ios' : '')
  				+' '+self.options.cssClass;
  
  		// prepare node
  		node.addClass(self.cssClass)
  			.on(mousedown, function() {
  				!enabled && self.enable();
  			});
  
  		// draggable closure
  		(function() {
  			var ltr, wzRect, wzBottom, wzBottom2, nodeStyle,
  				keyEvt = keydown + 'draggable' + ' keyup.' + namespace + 'draggable';
  			
  			/**
  			 * Base draggable options
  			 *
  			 * @type Object
  			 **/
  			self.draggable = {
  				appendTo   : node,
  				addClasses : false,
  				distance   : 4,
  				revert     : true,
  				refreshPositions : false,
  				cursor     : 'crosshair',
  				cursorAt   : {left : 50, top : 47},
  				scroll     : false,
  				start      : function(e, ui) {
  					var helper   = ui.helper,
  						targets  = $.grep(helper.data('files')||[], function(h) {
  							if (h) {
  								remember[h] = true;
  								return true;
  							}
  							return false;
  						}),
  						locked   = false,
  						cnt, h;
  					
  					// fix node size
  					nodeStyle = node.attr('style');
  					node.width(node.width()).height(node.height());
  					
  					// set var for drag()
  					ltr = (self.direction === 'ltr');
  					wzRect = self.getUI('workzone').data('rectangle');
  					wzBottom = wzRect.top + wzRect.height;
  					wzBottom2 = wzBottom - self.getUI('navdock').outerHeight(true);
  					
  					self.draggingUiHelper = helper;
  					cnt = targets.length;
  					while (cnt--) {
  						h = targets[cnt];
  						if (files[h].locked) {
  							locked = true;
  							helper.data('locked', true);
  							break;
  						}
  					}
  					!locked && self.trigger('lockfiles', {files : targets});
  		
  					helper.data('autoScrTm', setInterval(function() {
  						if (helper.data('autoScr')) {
  							self.autoScroll[helper.data('autoScr')](helper.data('autoScrVal'));
  						}
  					}, 50));
  				},
  				drag       : function(e, ui) {
  					var helper = ui.helper,
  						autoScr, autoUp, bottom;
  					
  					if ((autoUp = wzRect.top > e.pageY) || wzBottom2 < e.pageY) {
  						if (wzRect.cwdEdge > e.pageX) {
  							autoScr = (ltr? 'navbar' : 'cwd') + (autoUp? 'Up' : 'Down');
  						} else {
  							autoScr = (ltr? 'cwd' : 'navbar') + (autoUp? 'Up' : 'Down');
  						}
  						if (!autoUp) {
  							if (autoScr.substr(0, 3) === 'cwd') {
  								if (wzBottom < e.pageY) {
  									bottom = wzBottom;
  								} else {
  									autoScr = null;
  								}
  							} else {
  								bottom = wzBottom2;
  							}
  						}
  						if (autoScr) {
  							helper.data('autoScr', autoScr);
  							helper.data('autoScrVal', Math.pow((autoUp? wzRect.top - e.pageY : e.pageY - bottom), 1.3));
  						}
  					}
  					if (! autoScr) {
  						if (helper.data('autoScr')) {
  							helper.data('refreshPositions', 1).data('autoScr', null);
  						}
  					}
  					if (helper.data('refreshPositions') && $(this).elfUiWidgetInstance('draggable')) {
  						if (helper.data('refreshPositions') > 0) {
  							$(this).draggable('option', { refreshPositions : true, elfRefresh : true });
  							helper.data('refreshPositions', -1);
  						} else {
  							$(this).draggable('option', { refreshPositions : false, elfRefresh : false });
  							helper.data('refreshPositions', null);
  						}
  					}
  				},
  				stop       : function(e, ui) {
  					var helper = ui.helper,
  						files;
  					
  					$(document).off(keyEvt);
  					$(this).elfUiWidgetInstance('draggable') && $(this).draggable('option', { refreshPositions : false });
  					self.draggingUiHelper = null;
  					self.trigger('focus').trigger('dragstop');
  					if (! helper.data('droped')) {
  						files = $.grep(helper.data('files')||[], function(h) { return h? true : false ;});
  						self.trigger('unlockfiles', {files : files});
  						self.trigger('selectfiles', {files : self.selected()});
  					}
  					self.enable();
  					
  					// restore node style
  					node.attr('style', nodeStyle);
  					
  					helper.data('autoScrTm') && clearInterval(helper.data('autoScrTm'));
  				},
  				helper     : function(e, ui) {
  					var element = this.id ? $(this) : $(this).parents('[id]:first'),
  						helper  = $('<div class="elfinder-drag-helper"><span class="elfinder-drag-helper-icon-status"/></div>'),
  						icon    = function(f) {
  							var mime = f.mime, i, tmb = self.tmb(f);
  							i = '<div class="elfinder-cwd-icon elfinder-cwd-icon-drag '+self.mime2class(mime)+' ui-corner-all"/>';
  							if (tmb) {
  								i = $(i).addClass(tmb.className).css('background-image', "url('"+tmb.url+"')").get(0).outerHTML;
  							}
  							return i;
  						},
  						hashes, l, ctr;
  					
  					self.draggingUiHelper && self.draggingUiHelper.stop(true, true);
  					
  					self.trigger('dragstart', {target : element[0], originalEvent : e}, true);
  					
  					hashes = element.hasClass(self.res('class', 'cwdfile')) 
  						? self.selected() 
  						: [self.navId2Hash(element.attr('id'))];
  					
  					helper.append(icon(files[hashes[0]])).data('files', hashes).data('locked', false).data('droped', false).data('namespace', namespace).data('dropover', 0);
  		
  					if ((l = hashes.length) > 1) {
  						helper.append(icon(files[hashes[l-1]]) + '<span class="elfinder-drag-num">'+l+'</span>');
  					}
  					
  					$(document).on(keyEvt, function(e){
  						var chk = (e.shiftKey||e.ctrlKey||e.metaKey);
  						if (ctr !== chk) {
  							ctr = chk;
  							if (helper.is(':visible') && helper.data('dropover') && ! helper.data('droped')) {
  								helper.toggleClass('elfinder-drag-helper-plus', helper.data('locked')? true : ctr);
  								self.trigger(ctr? 'unlockfiles' : 'lockfiles', {files : hashes, helper: helper});
  							}
  						}
  					});
  					
  					return helper;
  				}
  			};
  		})();
  
  		// in getFileCallback set - change default actions on double click/enter/ctrl+enter
  		if (self.commands.getfile) {
  			if (typeof(self.options.getFileCallback) == 'function') {
  				self.bind('dblclick', function(e) {
  					e.preventDefault();
  					self.exec('getfile').fail(function() {
  						self.exec('open', e.data && e.data.file? [ e.data.file ]: void(0));
  					});
  				});
  				self.shortcut({
  					pattern     : 'enter',
  					description : self.i18n('cmdgetfile'),
  					callback    : function() { self.exec('getfile').fail(function() { self.exec(self.OS == 'mac' ? 'rename' : 'open'); }); }
  				})
  				.shortcut({
  					pattern     : 'ctrl+enter',
  					description : self.i18n(self.OS == 'mac' ? 'cmdrename' : 'cmdopen'),
  					callback    : function() { self.exec(self.OS == 'mac' ? 'rename' : 'open'); }
  				});
  			} else {
  				self.options.getFileCallback = null;
  			}
  		}
  
  		// load commands
  		$.each(self.commands, function(name, cmd) {
  			var proto = Object.assign({}, cmd.prototype),
  				extendsCmd, opts;
  			if ($.isFunction(cmd) && !self._commands[name] && (cmd.prototype.forceLoad || $.inArray(name, self.options.commands) !== -1)) {
  				extendsCmd = cmd.prototype.extendsCmd || '';
  				if (extendsCmd) {
  					if ($.isFunction(self.commands[extendsCmd])) {
  						cmd.prototype = Object.assign({}, base, new self.commands[extendsCmd](), cmd.prototype);
  					} else {
  						return true;
  					}
  				} else {
  					cmd.prototype = Object.assign({}, base, cmd.prototype);
  				}
  				self._commands[name] = new cmd();
  				cmd.prototype = proto;
  				opts = self.options.commandsOptions[name] || {};
  				if (extendsCmd && self.options.commandsOptions[extendsCmd]) {
  					opts = $.extend(true, {}, self.options.commandsOptions[extendsCmd], opts);
  				}
  				self._commands[name].setup(name, opts);
  				// setup linked commands
  				if (self._commands[name].linkedCmds.length) {
  					$.each(self._commands[name].linkedCmds, function(i, n) {
  						var lcmd = self.commands[n];
  						if ($.isFunction(lcmd) && !self._commands[n]) {
  							lcmd.prototype = base;
  							self._commands[n] = new lcmd();
  							self._commands[n].setup(n, self.options.commandsOptions[n]||{});
  						}
  					});
  				}
  			}
  		});
  
  		/**
  		 * UI nodes
  		 *
  		 * @type Object
  		 **/
  		self.ui = {
  			// container for nav panel and current folder container
  			workzone : $('<div/>').appendTo(node).elfinderworkzone(self),
  			// container for folders tree / places
  			navbar : $('<div/>').appendTo(node).elfindernavbar(self, self.options.uiOptions.navbar || {}),
  			// container for for preview etc at below the navbar
  			navdock : $('<div/>').appendTo(node).elfindernavdock(self, self.options.uiOptions.navdock || {}),
  			// contextmenu
  			contextmenu : $('<div/>').appendTo(node).elfindercontextmenu(self),
  			// overlay
  			overlay : $('<div/>').appendTo(node).elfinderoverlay({
  				show : function() { self.disable(); },
  				hide : function() { prevEnabled && self.enable(); }
  			}),
  			// current folder container
  			cwd : $('<div/>').appendTo(node).elfindercwd(self, self.options.uiOptions.cwd || {}),
  			// notification dialog window
  			notify : self.dialog('', {
  				cssClass      : 'elfinder-dialog-notify',
  				position      : self.options.notifyDialog.position,
  				absolute      : true,
  				resizable     : false,
  				autoOpen      : false,
  				closeOnEscape : false,
  				title         : '&nbsp;',
  				width         : parseInt(self.options.notifyDialog.width)
  			}),
  			statusbar : $('<div class="ui-widget-header ui-helper-clearfix ui-corner-bottom elfinder-statusbar"/>').hide().appendTo(node),
  			toast : $('<div class="elfinder-toast"/>').appendTo(node),
  			bottomtray : $('<div class="elfinder-bottomtray">').appendTo(node)
  		};
  
  		// load required ui
  		$.each(self.options.ui || [], function(i, ui) {
  			var name = 'elfinder'+ui,
  				opts = self.options.uiOptions[ui] || {};
  	
  			if (!self.ui[ui] && $.fn[name]) {
  				// regist to self.ui before make instance
  				self.ui[ui] = $('<'+(opts.tag || 'div')+'/>').appendTo(node);
  				self.ui[ui][name](self, opts);
  			}
  		});
  		
  		// update size	
  		self.resize(width, height);
  		
  		// make node resizable
  		if (self.options.resizable) {
  			node.resizable({
  				resize    : function(e, ui) {
  					self.resize(ui.size.width, ui.size.height);
  				},
  				handles   : 'se',
  				minWidth  : 300,
  				minHeight : 200
  			});
  			if (self.UA.Touch) {
  				node.addClass('touch-punch');
  			}
  		}
  
  		(function() {
  			var navbar = self.getUI('navbar'),
  				cwd    = self.getUI('cwd').parent();
  			
  			self.autoScroll = {
  				navbarUp   : function(v) {
  					navbar.scrollTop(Math.max(0, navbar.scrollTop() - v));
  				},
  				navbarDown : function(v) {
  					navbar.scrollTop(navbar.scrollTop() + v);
  				},
  				cwdUp     : function(v) {
  					cwd.scrollTop(Math.max(0, cwd.scrollTop() - v));
  				},
  				cwdDown   : function(v) {
  					cwd.scrollTop(cwd.scrollTop() + v);
  				}
  			};
  		})();
  
  		// Swipe on the touch devices to show/hide of toolbar or navbar
  		if (self.UA.Touch) {
  			(function() {
  				var lastX, lastY, nodeOffset, nodeWidth, nodeTop, navbarW, toolbarH,
  					navbar = self.getUI('navbar'),
  					toolbar = self.getUI('toolbar'),
  					moveEv = 'touchmove.stopscroll',
  					moveTm,
  					moveUpOn = function(e) {
  						var touches = e.originalEvent.touches || [{}],
  							y = touches[0].pageY || null;
  						if (!lastY || y < lastY) {
  							e.preventDefault();
  							moveTm && clearTimeout(moveTm);
  						}
  					},
  					moveDownOn = function(e) {
  						e.preventDefault();
  						moveTm && clearTimeout(moveTm);
  					},
  					moveOff = function() {
  						moveTm = setTimeout(function() {
  							node.off(moveEv);
  						}, 100);
  					},
  					handleW, handleH = 50;
  
  				navbar = navbar.children().length? navbar : null;
  				toolbar = toolbar.length? toolbar : null;
  				node.on('touchstart touchmove touchend', function(e) {
  					if (e.type === 'touchend') {
  						lastX = false;
  						lastY = false;
  						moveOff();
  						return;
  					}
  					
  					var touches = e.originalEvent.touches || [{}],
  						x = touches[0].pageX || null,
  						y = touches[0].pageY || null,
  						ltr = (self.direction === 'ltr'),
  						navbarMode, treeWidth, swipeX, moveX, toolbarT, mode;
  					
  					if (x === null || y === null || (e.type === 'touchstart' && touches.length > 1)) {
  						return;
  					}
  					
  					if (e.type === 'touchstart') {
  						nodeOffset = node.offset();
  						nodeWidth = node.width();
  						if (navbar) {
  							lastX = false;
  							if (navbar.is(':hidden')) {
  								if (! handleW) {
  									handleW = Math.max(50, nodeWidth / 10);
  								}
  								if ((ltr? (x - nodeOffset.left) : (nodeWidth + nodeOffset.left - x)) < handleW) {
  									lastX = x;
  								}
  							} else if (! e.originalEvent._preventSwipeX) {
  								navbarW = navbar.width();
  								if (ltr) {
  									swipeX = (x < nodeOffset.left + navbarW);
  								} else {
  									swipeX = (x > nodeOffset.left + nodeWidth - navbarW);
  								}
  								if (swipeX) {
  									handleW = Math.max(50, nodeWidth / 10);
  									lastX = x;
  								} else {
  									lastX = false;
  								}
  							}
  						}
  						if (toolbar) {
  							lastY = false;
  							if (! e.originalEvent._preventSwipeY) {
  								toolbarH = toolbar.height();
  								nodeTop = nodeOffset.top;
  								if (y - nodeTop < (toolbar.is(':hidden')? handleH : (toolbarH + 30))) {
  									lastY = y;
  									node.on(moveEv, toolbar.is(':hidden')? moveDownOn: moveUpOn);
  								}
  							}
  						}
  					} else {
  						if (navbar && lastX !== false) {
  							navbarMode = (ltr? (lastX > x) : (lastX < x))? 'navhide' : 'navshow';
  							moveX = Math.abs(lastX - x);
  							if (navbarMode === 'navhide' && moveX > navbarW * 0.6
  								|| (moveX > (navbarMode === 'navhide'? navbarW / 3 : 45)
  									&& (navbarMode === 'navshow'
  										|| (ltr? x < nodeOffset.left + 20 : x > nodeOffset.left + nodeWidth - 20)
  									))
  							) {
  								self.getUI('navbar').trigger(navbarMode, {handleW: handleW});
  								lastX = false;
  							}
  						}
  						if (toolbar && lastY !== false ) {
  							toolbarT = toolbar.offset().top;
  							if (Math.abs(lastY - y) > Math.min(45, toolbarH / 3)) {
  								mode = (lastY > y)? 'slideUp' : 'slideDown';
  								if (mode === 'slideDown' || toolbarT + 20 > y) {
  									if (toolbar.is(mode === 'slideDown' ? ':hidden' : ':visible')) {
  										toolbar.stop(true, true).trigger('toggle', {duration: 100, handleH: handleH});
  									}
  									lastY = false;
  								}
  							}
  						}
  					}
  				});
  			})();
  		}
  
  		if (self.dragUpload) {
  			// add event listener for HTML5 DnD upload
  			(function() {
  				var isin = function(e) {
  					return (e.target.nodeName !== 'TEXTAREA' && e.target.nodeName !== 'INPUT' && $(e.target).closest('div.ui-dialog-content').length === 0);
  				},
  				ent       = 'native-drag-enter',
  				disable   = 'native-drag-disable',
  				c         = 'class',
  				navdir    = self.res(c, 'navdir'),
  				droppable = self.res(c, 'droppable'),
  				dropover  = self.res(c, 'adroppable'),
  				arrow     = self.res(c, 'navarrow'),
  				clDropActive = self.res(c, 'adroppable'),
  				wz        = self.getUI('workzone'),
  				ltr       = (self.direction === 'ltr'),
  				clearTm   = function() {
  					autoScrTm && cancelAnimationFrame(autoScrTm);
  					autoScrTm = null;
  				},
  				wzRect, autoScrFn, autoScrTm;
  				
  				node.on('dragenter', function(e) {
  					clearTm();
  					if (isin(e)) {
  						e.preventDefault();
  						e.stopPropagation();
  						wzRect = wz.data('rectangle');
  					}
  				})
  				.on('dragleave', function(e) {
  					clearTm();
  					if (isin(e)) {
  						e.preventDefault();
  						e.stopPropagation();
  					}
  				})
  				.on('dragover', function(e) {
  					var autoUp;
  					if (isin(e)) {
  						e.preventDefault();
  						e.stopPropagation();
  						e.originalEvent.dataTransfer.dropEffect = 'none';
  						if (! autoScrTm) {
  							autoScrTm = requestAnimationFrame(function() {
  								var wzBottom = wzRect.top + wzRect.height,
  									wzBottom2 = wzBottom - self.getUI('navdock').outerHeight(true),
  									fn;
  								if ((autoUp = e.pageY < wzRect.top) || e.pageY > wzBottom2 ) {
  									if (wzRect.cwdEdge > e.pageX) {
  										fn = (ltr? 'navbar' : 'cwd') + (autoUp? 'Up' : 'Down');
  									} else {
  										fn = (ltr? 'cwd' : 'navbar') + (autoUp? 'Up' : 'Down');
  									}
  									if (!autoUp) {
  										if (fn.substr(0, 3) === 'cwd') {
  											if (wzBottom < e.pageY) {
  												wzBottom2 = wzBottom;
  											} else {
  												fn = '';
  											}
  										}
  									}
  									fn && self.autoScroll[fn](Math.pow((autoUp? wzRect.top - e.pageY : e.pageY - wzBottom2), 1.3));
  								}
  								autoScrTm = null;
  							});
  						}
  					} else {
  						clearTm();
  					}
  				})
  				.on('drop', function(e) {
  					clearTm();
  					if (isin(e)) {
  						e.stopPropagation();
  						e.preventDefault();
  					}
  				});
  				
  				node.on('dragenter', '.native-droppable', function(e){
  					if (e.originalEvent.dataTransfer) {
  						var $elm = $(e.currentTarget),
  							id   = e.currentTarget.id || null,
  							cwd  = null,
  							elfFrom;
  						if (!id) { // target is cwd
  							cwd = self.cwd();
  							$elm.data(disable, false);
  							try {
  								$.each(e.originalEvent.dataTransfer.types, function(i, v){
  									if (v.substr(0, 13) === 'elfinderfrom:') {
  										elfFrom = v.substr(13).toLowerCase();
  									}
  								});
  							} catch(e) {}
  						}
  						if (!cwd || (cwd.write && (!elfFrom || elfFrom !== (window.location.href + cwd.hash).toLowerCase()))) {
  							e.preventDefault();
  							e.stopPropagation();
  							$elm.data(ent, true);
  							$elm.addClass(clDropActive);
  						} else {
  							$elm.data(disable, true);
  						}
  					}
  				})
  				.on('dragleave', '.native-droppable', function(e){
  					if (e.originalEvent.dataTransfer) {
  						var $elm = $(e.currentTarget);
  						e.preventDefault();
  						e.stopPropagation();
  						if ($elm.data(ent)) {
  							$elm.data(ent, false);
  						} else {
  							$elm.removeClass(clDropActive);
  						}
  					}
  				})
  				.on('dragover', '.native-droppable', function(e){
  					if (e.originalEvent.dataTransfer) {
  						var $elm = $(e.currentTarget);
  						e.preventDefault();
  						e.stopPropagation();
  						e.originalEvent.dataTransfer.dropEffect = $elm.data(disable)? 'none' : 'copy';
  						$elm.data(ent, false);
  					}
  				})
  				.on('drop', '.native-droppable', function(e){
  					if (e.originalEvent && e.originalEvent.dataTransfer) {
  						var $elm = $(e.currentTarget),
  							id;
  						e.preventDefault();
  						e.stopPropagation();
  						$elm.removeClass(clDropActive);
  						if (e.currentTarget.id) {
  							id = $elm.hasClass(navdir)? self.navId2Hash(e.currentTarget.id) : self.cwdId2Hash(e.currentTarget.id);
  						} else {
  							id = self.cwd().hash;
  						}
  						e.originalEvent._target = id;
  						self.exec('upload', {dropEvt: e.originalEvent, target: id}, void 0, id);
  					}
  				});
  			})();
  		}
  
  		// trigger event cssloaded if cddAutoLoad disabled
  		if (self.cssloaded === null) {
  			// check css loaded and remove hide
  			(function() {
  				var loaded = function() {
  						node.data('cssautoloadHide').remove();
  						node.removeData('cssautoloadHide');
  						self.cssloaded = true;
  						requestAnimationFrame(function() {
  							self.trigger('cssloaded');
  						});
  					},
  					cnt, fi;
  				if (node.css('visibility') === 'hidden') {
  					cnt = 1000; // timeout 10 secs
  					fi  = setInterval(function() {
  						if (--cnt < 0 || node.css('visibility') !== 'hidden') {
  							clearInterval(fi);
  							loaded();
  						}
  					}, 10);
  				} else {
  					loaded();
  				}
  			})();
  		} else {
  			self.cssloaded = true;
  			self.trigger('cssloaded');
  		}
  
  		// calculate elFinder node z-index
  		self.zIndexCalc();
  
  		// send initial request and start to pray >_<
  		self.trigger('init')
  			.request({
  				data        : {cmd : 'open', target : self.startDir(), init : 1, tree : 1}, 
  				preventDone : true,
  				notify      : {type : 'open', cnt : 1, hideCnt : true},
  				freeze      : true
  			})
  			.fail(function() {
  				self.trigger('fail').disable().lastDir('');
  				listeners = {};
  				shortcuts = {};
  				$(document).add(node).off('.'+namespace);
  				self.trigger = function() { };
  			})
  			.done(function(data) {
  				var trashDisable = function(th) {
  						var src = self.file(self.trashes[th]),
  							d = self.options.debug,
  							error;
  						
  						if (src && src.volumeid) {
  							delete self.volOptions[src.volumeid].trashHash;
  						}
  						self.trashes[th] = false;
  						self.debug('backend-error', 'Trash hash "'+th+'" was not found or not writable.');
  					},
  					toChkTh = {};
  				
  				// regist rawStringDecoder
  				if (self.options.rawStringDecoder) {
  					self.registRawStringDecoder(self.options.rawStringDecoder);
  				}
  
  				// re-calculate elFinder node z-index
  				self.zIndexCalc();
  				
  				self.load().debug('api', self.api);
  				// update ui's size after init
  				node.trigger('resize');
  				// initial open
  				open(data);
  				self.trigger('open', data, false);
  				self.trigger('opendone');
  				
  				if (inFrame && self.options.enableAlways) {
  					$(window).trigger('focus');
  				}
  				
  				// check self.trashes
  				$.each(self.trashes, function(th) {
  					var dir = self.file(th),
  						src;
  					if (! dir) {
  						toChkTh[th] = true;
  					} else if (dir.mime !== 'directory' || ! dir.write) {
  						trashDisable(th);
  					}
  				});
  				if (Object.keys(toChkTh).length) {
  					self.request({
  						data : {cmd : 'info', targets : Object.keys(toChkTh)},
  						preventDefault : true
  					}).done(function(data) {
  						if (data && data.files) {
  							$.each(data.files, function(i, dir) {
  								if (dir.mime === 'directory' && dir.write) {
  									delete toChkTh[dir.hash];
  								}
  							});
  						}
  					}).always(function() {
  						$.each(toChkTh, trashDisable);
  					});
  				}
  				// to enable / disable
  				self[self.options.enableAlways? 'enable' : 'disable']();
  			});
  		
  		// self.timeEnd('load');
  		// End of bootUp()
  	};
  	
  	// call bootCallback function with elFinder instance, extraObject - { dfrdsBeforeBootup: dfrdsBeforeBootup }
  	if (bootCallback && typeof bootCallback === 'function') {
  		self.bootCallback = bootCallback;
  		bootCallback.call(node.get(0), self, { dfrdsBeforeBootup: dfrdsBeforeBootup });
  	}
  	
  	// call dfrdsBeforeBootup functions then boot up elFinder
  	$.when.apply(null, dfrdsBeforeBootup).done(function() {
  		bootUp();
  	}).fail(function(error) {
  		self.error(error);
  	});
  }