/*! * elFinder - file manager for web * Version 2.0 rc1 (2012-09-28) * http://elfinder.org * * Copyright 2009-2012, Studio 42 * Licensed under a 3 clauses BSD license */ (function($) { /* * File: /home/osc/elFinder-build/elFinder/js/elFinder.js */ /** * @class elFinder - file manager for web * * @author Dmitry (dio) Levashov **/ window.elFinder = function(node, opts) { this.time('load'); var self = this, /** * Node on which elfinder creating * * @type jQuery **/ node = $(node), /** * Store node contents. * * @see this.destroy * @type jQuery **/ prevContent = $('
').append(node.contents()), /** * Store node inline styles * * @see this.destroy * @type String **/ prevStyle = node.attr('style'), /** * Instance ID. Required to get/set cookie * * @type String **/ id = node.attr('id') || '', /** * Events namespace * * @type String **/ namespace = 'elfinder-'+(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, /** * Is shortcuts/commands enabled * * @type Boolean **/ enabled = true, /** * Store enabled value before ajax requiest * * @type Boolean **/ prevEnabled = true, /** * 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', 'dragstart', 'dragstop'], /** * Rules to validate data from backend * * @type Object **/ rules = {}, /** * Current working directory hash * * @type String **/ cwd = '', /** * Current working directory options * * @type Object **/ cwdOptions = { path : '', url : '', tmbUrl : '', disabled : [], separator : '/', archives : [], extract : [], copyOverwrite : true, tmb : false // old API }, /** * Files/dirs cache * * @type Object **/ files = {}, /** * 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 Array **/ remember = [], /** * Queue for 'open' requests * * @type Array **/ queue = [], /** * Commands prototype * * @type Object **/ base = new self.command(self), /** * elFinder node width * * @type String * @default "auto" **/ width = 'auto', /** * elFinder node height * * @type Number * @default 400 **/ height = 400, beeper = $(document.createElement('audio')).hide().appendTo('body')[0], syncInterval, open = function(data) { if (data.init) { // init - reset cache files = {}; } else { // remove only files from prev cwd for (var i in files) { if (files.hasOwnProperty(i) && files[i].mime != 'directory' && files[i].phash == cwd && $.inArray(i, remember) === -1) { delete files[i]; } } } cwd = data.cwd.hash; cache(data.files); if (!files[cwd]) { cache([data.cwd]); } self.lastDir(cwd); }, /** * Store info about files/dirs in "files" object. * * @param Array files * @return void **/ cache = function(data) { var l = data.length, f; while (l--) { f = data[l]; if (f.name && f.hash && f.mime) { if (!f.phash) { var name = 'volume_'+f.name, i18 = self.i18n(name); if (name != i18) { f.i18 = i18; } } files[f.hash] = f; } } }, /** * Exec shortcut * * @param jQuery.Event keydown/keypress event * @return void */ execShortcut = function(e) { var code = e.keyCode, ctrlKey = !!(e.ctrlKey || e.metaKey); 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 == 9 && !$(e.target).is(':input')) { e.preventDefault(); } } }, date = new Date(), utc, i18n ; /** * 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; /** * User os. Required to bind native shortcuts for open/rename * * @type String **/ this.OS = navigator.userAgent.indexOf('Mac') !== -1 ? 'mac' : navigator.userAgent.indexOf('Win') !== -1 ? 'win' : 'other'; /** * Configuration options * * @type Object **/ this.options = $.extend(true, {}, this._options, opts||{}); if (opts.ui) { this.options.ui = opts.ui; } if (opts.commands) { this.options.commands = opts.commands; } if (opts.uiOptions && opts.uiOptions.toolbar) { this.options.uiOptions.toolbar = opts.uiOptions.toolbar; } $.extend(this.options.contextmenu, opts.contextmenu); /** * Ajax request type * * @type String * @default "get" **/ this.requestType = /^(get|post)$/i.test(this.options.requestType) ? this.options.requestType.toLowerCase() : 'get', /** * Any data to send across every ajax request * * @type Object * @default {} **/ this.customData = $.isPlainObject(this.options.customData) ? this.options.customData : {}; /** * ID. Required to create unique cookie name * * @type String **/ this.id = id; /** * URL to upload files * * @type String **/ this.uploadURL = opts.urlUpload || opts.url; /** * Events namespace * * @type String **/ this.namespace = namespace; /** * Interface language * * @type String * @default "en" **/ this.lang = this.i18[this.options.lang] && this.i18[this.options.lang].messages ? this.options.lang : 'en'; i18n = this.lang == 'en' ? this.i18['en'] : $.extend(true, {}, this.i18['en'], this.i18[this.lang]); /** * Interface direction * * @type String * @default "ltr" **/ this.direction = i18n.direction; /** * i18 messages * * @type Object **/ this.messages = i18n.messages; /** * Date/time format * * @type String * @default "m.d.Y" **/ this.dateFormat = this.options.dateFormat || i18n.dateFormat; /** * Date format like "Yesterday 10:20:12" * * @type String * @default "{day} {time}" **/ this.fancyFormat = this.options.fancyDateFormat || i18n.fancyDateFormat; /** * 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'; /** * Css classes * * @type String **/ this.cssClass = 'ui-helper-reset ui-helper-clearfix ui-widget ui-widget-content ui-corner-all elfinder elfinder-'+(this.direction == 'rtl' ? 'rtl' : 'ltr')+' '+this.options.cssClass; /** * Method to store/fetch data * * @type Function **/ this.storage = (function() { try { return 'localStorage' in window && window['localStorage'] !== null ? self.localStorage : self.cookie; } catch (e) { return self.cookie; } })(); 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.sortRules = $.extend(true, {}, this._sortRules, this.options.sortsRules); $.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; /** * Base draggable options * * @type Object **/ this.draggable = { appendTo : 'body', addClasses : true, delay : 30, revert : true, refreshPositions : true, cursor : 'move', cursorAt : {left : 50, top : 47}, drag : function(e, ui) { if (! ui.helper.data('locked')) { ui.helper.toggleClass('elfinder-drag-helper-plus', e.shiftKey||e.ctrlKey||e.metaKey); } }, start : function(e, ui) { var targets = $.map(ui.helper.data('files')||[], function(h) { return h || null ;}), cnt, h; cnt = targets.length; while (cnt--) { h = targets[cnt]; if (files[h].locked) { ui.helper.addClass('elfinder-drag-helper-plus').data('locked', true); break; } } }, stop : function() { self.trigger('focus').trigger('dragstop'); }, helper : function(e, ui) { var element = this.id ? $(this) : $(this).parents('[id]:first'), helper = $('
'), icon = function(mime) { return '
'; }, hashes, l; self.trigger('dragstart', {target : element[0], originalEvent : e}); hashes = element.is('.'+self.res('class', 'cwdfile')) ? self.selected() : [self.navId2Hash(element.attr('id'))]; helper.append(icon(files[hashes[0]].mime)).data('files', hashes).data('locked', false); if ((l = hashes.length) > 1) { helper.append(icon(files[hashes[l-1]].mime) + ''+l+''); } return helper; } }; /** * Base droppable options * * @type Object **/ this.droppable = { // greedy : true, tolerance : 'pointer', accept : '.elfinder-cwd-file-wrapper,.elfinder-navbar-dir,.elfinder-cwd-file', hoverClass : this.res('class', 'adroppable'), drop : function(e, ui) { var dst = $(this), targets = $.map(ui.helper.data('files')||[], function(h) { return h || null }), result = [], c = 'class', cnt, hash, i, h; if (dst.is('.'+self.res(c, 'cwd'))) { hash = cwd; } else if (dst.is('.'+self.res(c, 'cwdfile'))) { hash = dst.attr('id'); } else if (dst.is('.'+self.res(c, 'navdir'))) { hash = self.navId2Hash(dst.attr('id')); } cnt = targets.length; while (cnt--) { h = targets[cnt]; // ignore drop into itself or in own location h != hash && files[h].phash != hash && result.push(h); } if (result.length) { ui.helper.hide(); self.clipboard(result, !(e.ctrlKey||e.shiftKey||e.metaKey||ui.helper.data('locked'))); self.exec('paste', hash); self.trigger('drop', {files : targets}); } } }; /** * Return true if filemanager is active * * @return Boolean **/ this.enabled = function() { return node.is(':visible') && enabled; } /** * Return true if filemanager is visible * * @return Boolean **/ this.visible = function() { return node.is(':visible'); } /** * Return root dir hash for current working directory * * @return String */ this.root = function(hash) { var dir = files[hash || cwd], i; while (dir && dir.phash) { dir = files[dir.phash] } if (dir) { return dir.hash; } while (i in files && files.hasOwnProperty(i)) { dir = files[i] if (!dir.phash && !dir.mime == 'directory' && 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 * @return mixed */ this.option = function(name) { return cwdOptions[name]||''; } /** * Return file data from current dir or tree by it's hash * * @param String file hash * @return Object */ this.file = function(hash) { return files[hash]; }; /** * Return all cached files * * @return Array */ this.files = function() { return $.extend(true, {}, files); } /** * Return list of file parents hashes include file hash * * @param String file hash * @return Array */ this.parents = function(hash) { var parents = [], dir; while ((dir = this.file(hash))) { parents.unshift(dir.hash); hash = dir.phash; } return parents; } this.path2array = function(hash, i18) { var file, path = []; while (hash && (file = files[hash]) && file.hash) { path.unshift(i18 && file.i18 ? file.i18 : file.name); hash = file.phash; } return path; } /** * Return file path * * @param Object file * @return String */ this.path = function(hash, i18) { return files[hash] && files[hash].path ? files[hash].path : this.path2array(hash, i18).join(cwdOptions.separator); } /** * Return file url if set * * @param Object file * @return String */ this.url = function(hash) { var file = files[hash]; if (!file || !file.read) { return ''; } if (file.url) { return file.url; } if (cwdOptions.url) { return cwdOptions.url + $.map(this.path2array(hash), function(n) { return encodeURIComponent(n); }).slice(1).join('/') } var params = $.extend({}, this.customData, { cmd: 'file', target: file.hash }); if (this.oldAPI) { params.cmd = 'open'; params.current = file.phash; } return this.options.url + (this.options.url.indexOf('?') === -1 ? '?' : '&') + $.param(params, true); } /** * Return thumbnail url * * @param String file hash * @return String */ this.tmb = function(hash) { var file = files[hash], url = file && file.tmb && file.tmb != 1 ? cwdOptions['tmbUrl'] + file.tmb : ''; if (url && ($.browser.opera || $.browser.msie)) { url += '?_=' + new Date().getTime(); } return url; } /** * 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] ? $.extend({}, 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); } /** * Proccess ajax request. * Fired events : * @todo * @example * @todo * @return $.Deferred */ this.request = function(options) { var self = this, o = this.options, dfrd = $.Deferred(), // request data data = $.extend({}, o.customData, {mimes : o.onlyMimes}, options.data || options), // command name cmd = data.cmd, // call default fail callback (display error dialog) ? deffail = !(options.preventDefault || options.preventFail), // call default success callback ? defdone = !(options.preventDefault || options.preventDone), // options for notify dialog notify = $.extend({}, options.notify), // do not normalize data - return as is raw = !!options.raw, // sync files on request fail syncOnFail = options.syncOnFail, // open notify dialog timeout timeout, // request options options = $.extend({ url : o.url, async : true, type : this.requestType, dataType : 'json', cache : false, // timeout : 100, data : data }, options.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); cmd == 'open' && open($.extend(true, {}, data)); // 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); // fire event with command name self.trigger(cmd, data); // 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; switch (status) { case 'abort': error = xhr.quiet ? '' : ['errConnect', 'errAbort']; break; case 'timeout': error = ['errConnect', 'errTimeout']; break; case 'parsererror': error = ['errResponse', 'errDataNotJSON']; break; default: if (xhr.status == 403) { error = ['errConnect', 'errAccess']; } else if (xhr.status == 404) { error = ['errConnect', 'errNotFound']; } else { error = 'errConnect'; } } 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) { if (raw) { return dfrd.resolve(response); } if (!response) { return dfrd.reject(['errResponse', 'errDataEmpty'], xhr); } else if (!$.isPlainObject(response)) { return dfrd.reject(['errResponse', 'errDataNotJSON'], xhr); } else if (response.error) { return dfrd.reject(response.error, xhr); } else if (!self.validResponse(cmd, response)) { return dfrd.reject('errResponse', xhr); } response = self.normalize(response); if (!self.api) { self.api = response.api || 1; self.newAPI = self.api >= 2; self.oldAPI = !self.newAPI; } if (response.options) { cwdOptions = $.extend({}, cwdOptions, response.options); } if (response.netDrivers) { self.netDrivers = response.netDrivers; } dfrd.resolve(response); response.debug && self.debug('backend-debug', response.debug); }, xhr, _xhr ; defdone && dfrd.done(done); dfrd.fail(function(error) { if (error) { deffail ? self.error(error) : self.debug('error', self.i18n(error)); } }) if (!cmd) { return dfrd.reject('errCmdReq'); } if (syncOnFail) { dfrd.fail(function(error) { error && self.sync(); }); } if (notify.type && notify.cnt) { 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); }); } // quiet abort not completed "open" requests if (cmd == 'open') { while ((_xhr = queue.pop())) { if (_xhr.state() == 'pending') { _xhr.quiet = true; _xhr.abort(); } } } delete options.preventFail xhr = this.transport.send(options).fail(error).done(success); // this.transport.send(options) // add "open" xhr into queue if (cmd == 'open') { queue.unshift(xhr); dfrd.always(function() { var ndx = $.inArray(xhr, queue); ndx !== -1 && queue.splice(ndx, 1); }); } return dfrd; }; /** * Compare current files cache with new files and return diff * * @param Array new files * @return Object */ this.diff = function(incoming) { var raw = {}, added = [], removed = [], changed = [], 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; }); // find removed $.each(files, function(hash, f) { !raw[hash] && removed.push(hash); }); // compare files $.each(raw, function(hash, file) { var origin = files[hash]; if (!origin) { added.push(file); } else { $.each(file, function(prop) { if (file[prop] != origin[prop]) { changed.push(file) return false; } }); } }); // 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() { var self = this, dfrd = $.Deferred().done(function() { self.trigger('sync'); }), opts1 = { data : {cmd : 'open', init : 1, target : cwd, tree : this.ui.tree ? 1 : 0}, preventDefault : true }, opts2 = { data : {cmd : 'tree', target : (cwd == this.root())? cwd : this.file(cwd).phash}, preventDefault : true }; $.when( this.request(opts1), this.request(opts2) ) .fail(function(error) { dfrd.reject(error); error && self.request({ data : {cmd : 'open', target : self.lastDir(''), tree : 1, init : 1}, notify : {type : 'open', cnt : 1, hideCnt : true} }); }) .done(function(odata, pdata) { var diff = self.diff(odata.files.concat(pdata && pdata.tree ? pdata.tree : [])); diff.added.push(odata.cwd) diff.removed.length && self.remove(diff); diff.added.length && self.add(diff); diff.changed.length && self.change(diff); return dfrd.resolve(diff); }); return dfrd; } this.upload = function(files) { return this.transport.upload(files, this); } /** * 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 * @return elFinder */ this.bind = function(event, callback) { var i; if (typeof(callback) == 'function') { event = ('' + event).toLowerCase().split(/\s+/); for (i = 0; i < event.length; i++) { if (listeners[event[i]] === void(0)) { listeners[event[i]] = []; } listeners[event[i]].push(callback); } } return this; }; /** * Remove event listener if exists * * @param String event name * @param Function callback * @return elFinder */ this.unbind = function(event, callback) { var l = listeners[('' + event).toLowerCase()] || [], i = l.indexOf(callback); i > -1 && l.splice(i, 1); //delete callback; // need this? callback = null return this; }; /** * Fire event - send notification to all event listeners * * @param String event type * @param Object data to send across event * @return elFinder */ this.trigger = function(event, data) { var event = event.toLowerCase(), handlers = listeners[event] || [], i, j; this.debug('event-'+event, data) if (handlers.length) { event = $.Event(event); for (i = 0; i < handlers.length; i++) { // to avoid data modifications. remember about "sharing" passing arguments in js :) event.data = $.extend(true, {}, data); try { if (handlers[i](event, this) === false || event.isDefaultPrevented()) { this.debug('event-stoped', event.type); break; } } catch (ex) { window.console && window.console.log && window.console.log(ex); } } } return this; } /** * 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) : $.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.push(hash); 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 * @return Boolean */ this.isCommandEnabled = function(name) { return this._commands[name] ? $.inArray(name, cwdOptions.disabled) === -1 : false; } /** * Exec command and return result; * * @param String command name * @param String|Array usualy files hashes * @param String|Array command options * @return $.Deferred */ this.exec = function(cmd, files, opts) { return this._commands[cmd] && this.isCommandEnabled(cmd) ? this._commands[cmd].exec(files, opts) : $.Deferred().reject('No such command'); } /** * Create and return dialog. * * @param String|DOMElement dialog content * @param Object dialog options * @return jQuery */ this.dialog = function(content, options) { return $('
').append(content).appendTo(node).elfinderdialog(options); } /** * Return UI widget or node * * @param String ui name * @return jQuery */ this.getUI = function(ui) { return this.ui[ui] || node; } this.command = function(name) { return name === void(0) ? this._commands : this._commands[name]; } /** * Resize elfinder node * * @param String|Number width * @param Number height * @return void */ this.resize = function(w, h) { node.css('width', w).height(h).trigger('resize'); this.trigger('resize', {width : node.width(), height : node.height()}); } /** * 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() { this.disable().trigger('hide'); node.hide(); } /** * Destroy this elFinder instance * * @return void **/ this.destroy = function() { if (node && node[0].elfinder) { this.trigger('destroy').disable(); listeners = {}; shortcuts = {}; $(document).add(node).unbind('.'+this.namespace); self.trigger = function() { } node.children().remove(); node.append(prevContent.contents()).removeClass(this.cssClass).attr('style', prevStyle); node[0].elfinder = null; if (syncInterval) { clearInterval(syncInterval); } } } /************* init stuffs ****************/ // check jquery ui if (!($.fn.selectable && $.fn.draggable && $.fn.droppable)) { return alert(this.i18n('errJqui')); } // check node if (!node.length) { return alert(this.i18n('errNode')); } // check connector url if (!this.options.url) { return alert(this.i18n('errURL')); } $.extend($.ui.keyCode, { 'F1' : 112, 'F2' : 113, 'F3' : 114, 'F4' : 115, 'F5' : 116, 'F6' : 117, 'F7' : 118, 'F8' : 119, 'F9' : 120 }); this.dragUpload = false; this.xhrUpload = typeof XMLHttpRequestUpload != '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.transport.upload = $.proxy(this.uploads.xhr, this); this.dragUpload = true; } else { this.transport.upload = $.proxy(this.uploads.iframe, this); } /** * Alias for this.trigger('error', {error : 'message'}) * * @param String error message * @return elFinder **/ this.error = function() { var arg = arguments[0]; return arguments.length == 1 && typeof(arg) == 'function' ? self.bind('error', arg) : self.trigger('error', {error : arg}); } // create bind/trigger aliases for build-in events $.each(['enable', 'disable', 'load', 'open', 'reload', 'select', 'add', 'remove', 'change', 'dblclick', 'getfile', 'lockfiles', 'unlockfiles', 'dragstart', 'dragstop', 'search', 'searchend', 'viewchange'], 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.visible() && self.ui.overlay.is(':hidden')) { enabled = true; $('texarea:focus,input:focus,button').blur(); node.removeClass('elfinder-disabled'); } }) .disable(function() { prevEnabled = enabled; enabled = false; node.addClass('elfinder-disabled'); }) .open(function() { selected = []; }) .select(function(e) { selected = $.map(e.data.selected || e.data.value|| [], function(hash) { return files[hash] ? hash : null; }); }) .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'); }; self.dialog(''+self.i18n(e.data.error), opts); }) .bind('tree parents', function(e) { cache(e.data.tree || []); }) .bind('tmb', function(e) { $.each(e.data.images||[], function(hash, tmb) { if (files[hash]) { files[hash].tmb = tmb; } }) }) .add(function(e) { cache(e.data.added||[]); }) .change(function(e) { $.each(e.data.changed||[], function(i, file) { var hash = file.hash; files[hash] = files[hash] ? $.extend(files[hash], file) : file; }); }) .remove(function(e) { var removed = e.data.removed||[], l = removed.length, rm = function(hash) { var file = files[hash]; if (file) { if (file.mime == 'directory' && file.dirs) { $.each(files, function(h, f) { f.phash == hash && rm(h); }); } delete files[hash]; } }; while (l--) { rm(removed[l]); } }) .bind('search', function(e) { cache(e.data.files); }) .bind('rm', function(e) { var play = beeper.canPlayType && beeper.canPlayType('audio/wav; codecs="1"'); play && play != '' && play != 'no' && $(beeper).html('')[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); // in getFileCallback set - change default actions on double click/enter/ctrl+enter if (typeof(this.options.getFileCallback) == 'function' && this.commands.getfile) { this.bind('dblclick', function(e) { e.preventDefault(); self.exec('getfile').fail(function() { self.exec('open'); }); }); this.shortcut({ pattern : 'enter', description : this.i18n('cmdgetfile'), callback : function() { self.exec('getfile').fail(function() { self.exec(self.OS == 'mac' ? 'rename' : 'open') }) } }) .shortcut({ pattern : 'ctrl+enter', description : this.i18n(this.OS == 'mac' ? 'cmdrename' : 'cmdopen'), callback : function() { self.exec(self.OS == 'mac' ? 'rename' : 'open') } }); } /** * Loaded commands * * @type Object **/ this._commands = {}; if (!$.isArray(this.options.commands)) { this.options.commands = []; } // check required commands $.each(['open', 'reload', 'back', 'forward', 'up', 'home', 'info', 'quicklook', 'getfile', 'help'], function(i, cmd) { $.inArray(cmd, self.options.commands) === -1 && self.options.commands.push(cmd); }); // load commands $.each(this.options.commands, function(i, name) { var cmd = self.commands[name]; if ($.isFunction(cmd) && !self._commands[name]) { cmd.prototype = base; self._commands[name] = new cmd(); self._commands[name].setup(name, self.options.commandsOptions[name]||{}); } }); // prepare node node.addClass(this.cssClass) .bind(mousedown, function() { !enabled && self.enable(); }); /** * UI nodes * * @type Object **/ this.ui = { // container for nav panel and current folder container workzone : $('
').appendTo(node).elfinderworkzone(this), // container for folders tree / places navbar : $('
').appendTo(node).elfindernavbar(this, this.options.uiOptions.navbar || {}), // contextmenu contextmenu : $('
').appendTo(node).elfindercontextmenu(this), // overlay overlay : $('
').appendTo(node).elfinderoverlay({ show : function() { self.disable(); }, hide : function() { prevEnabled && self.enable(); } }), // current folder container cwd : $('
').appendTo(node).elfindercwd(this, this.options.uiOptions.cwd || {}), // notification dialog window notify : this.dialog('', { cssClass : 'elfinder-dialog-notify', position : {top : '12px', right : '12px'}, resizable : false, autoOpen : false, title : ' ', width : 280 }), statusbar : $('
').hide().appendTo(node) } // load required ui $.each(this.options.ui || [], function(i, ui) { var name = 'elfinder'+ui, opts = self.options.uiOptions[ui] || {}; if (!self.ui[ui] && $.fn[name]) { self.ui[ui] = $('<'+(opts.tag || 'div')+'/>').appendTo(node)[name](self, opts); } }); // store instance in node node[0].elfinder = this; // make node resizable this.options.resizable && $.fn.resizable && node.resizable({ handles : 'se', minWidth : 300, minHeight : 200 }); if (this.options.width) { width = this.options.width; } if (this.options.height) { height = parseInt(this.options.height); } // update size self.resize(width, height); // attach events to document $(document) // disable elfinder on click outside elfinder .bind('click.'+this.namespace, function(e) { enabled && !$(e.target).closest(node).length && self.disable(); }) // exec shortcuts .bind(keydown+' '+keypress, execShortcut); // send initial request and start to pray >_< this.trigger('init') .request({ data : {cmd : 'open', target : self.lastDir(), init : 1, tree : this.ui.tree ? 1 : 0}, preventDone : true, notify : {type : 'open', cnt : 1, hideCnt : true}, freeze : true }) .fail(function() { self.trigger('fail').disable().lastDir(''); listeners = {}; shortcuts = {}; $(document).add(node).unbind('.'+this.namespace); self.trigger = function() { }; }) .done(function(data) { self.load().debug('api', self.api); data = $.extend(true, {}, data); open(data); self.trigger('open', data); }); // update ui's size after init this.one('load', function() { node.trigger('resize'); if (self.options.sync > 1000) { syncInterval = setInterval(function() { self.sync(); }, self.options.sync) } }); // self.timeEnd('load'); } /** * Prototype * * @type Object */ elFinder.prototype = { res : function(type, id) { return this.resources[type] && this.resources[type][id]; }, /** * Internationalization object * * @type Object */ i18 : { en : { translator : '', language : 'English', direction : 'ltr', dateFormat : 'd.m.Y H:i', fancyDateFormat : '$1 H:i', messages : {} }, months : ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], monthsShort : ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], days : ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], daysShort : ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] }, /** * File mimetype to kind mapping * * @type Object */ kinds : { 'unknown' : 'Unknown', 'directory' : 'Folder', 'symlink' : 'Alias', 'symlink-broken' : 'AliasBroken', 'application/x-empty' : 'TextPlain', 'application/postscript' : 'Postscript', 'application/vnd.ms-office' : 'MsOffice', 'application/vnd.ms-word' : 'MsWord', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' : 'MsWord', 'application/vnd.ms-word.document.macroEnabled.12' : 'MsWord', 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' : 'MsWord', 'application/vnd.ms-word.template.macroEnabled.12' : 'MsWord', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' : 'MsWord', 'application/vnd.ms-excel' : 'MsExcel', 'application/vnd.ms-excel.sheet.macroEnabled.12' : 'MsExcel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' : 'MsExcel', 'application/vnd.ms-excel.template.macroEnabled.12' : 'MsExcel', 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' : 'MsExcel', 'application/vnd.ms-excel.addin.macroEnabled.12' : 'MsExcel', 'application/vnd.ms-powerpoint' : 'MsPP', 'application/vnd.openxmlformats-officedocument.presentationml.presentation' : 'MsPP', 'application/vnd.ms-powerpoint.presentation.macroEnabled.12' : 'MsPP', 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' : 'MsPP', 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' : 'MsPP', 'application/vnd.openxmlformats-officedocument.presentationml.template' : 'MsPP', 'application/vnd.ms-powerpoint.template.macroEnabled.12' : 'MsPP', 'application/vnd.ms-powerpoint.addin.macroEnabled.12' : 'MsPP', 'application/vnd.openxmlformats-officedocument.presentationml.slide' : 'MsPP', 'application/vnd.ms-powerpoint.slide.macroEnabled.12' : 'MsPP', 'application/pdf' : 'PDF', 'application/xml' : 'XML', 'application/vnd.oasis.opendocument.text' : 'OO', 'application/vnd.oasis.opendocument.text-template' : 'OO', 'application/vnd.oasis.opendocument.text-web' : 'OO', 'application/vnd.oasis.opendocument.text-master' : 'OO', 'application/vnd.oasis.opendocument.graphics' : 'OO', 'application/vnd.oasis.opendocument.graphics-template' : 'OO', 'application/vnd.oasis.opendocument.presentation' : 'OO', 'application/vnd.oasis.opendocument.presentation-template' : 'OO', 'application/vnd.oasis.opendocument.spreadsheet' : 'OO', 'application/vnd.oasis.opendocument.spreadsheet-template' : 'OO', 'application/vnd.oasis.opendocument.chart' : 'OO', 'application/vnd.oasis.opendocument.formula' : 'OO', 'application/vnd.oasis.opendocument.database' : 'OO', 'application/vnd.oasis.opendocument.image' : 'OO', 'application/vnd.openofficeorg.extension' : 'OO', 'application/x-shockwave-flash' : 'AppFlash', 'application/flash-video' : 'Flash video', 'application/x-bittorrent' : 'Torrent', 'application/javascript' : 'JS', 'application/rtf' : 'RTF', 'application/rtfd' : 'RTF', 'application/x-font-ttf' : 'TTF', 'application/x-font-otf' : 'OTF', 'application/x-rpm' : 'RPM', 'application/x-web-config' : 'TextPlain', 'application/xhtml+xml' : 'HTML', 'application/docbook+xml' : 'DOCBOOK', 'application/x-awk' : 'AWK', 'application/x-gzip' : 'GZIP', 'application/x-bzip2' : 'BZIP', 'application/zip' : 'ZIP', 'application/x-zip' : 'ZIP', 'application/x-rar' : 'RAR', 'application/x-tar' : 'TAR', 'application/x-7z-compressed' : '7z', 'application/x-jar' : 'JAR', 'text/plain' : 'TextPlain', 'text/x-php' : 'PHP', 'text/html' : 'HTML', 'text/javascript' : 'JS', 'text/css' : 'CSS', 'text/rtf' : 'RTF', 'text/rtfd' : 'RTF', 'text/x-c' : 'C', 'text/x-csrc' : 'C', 'text/x-chdr' : 'CHeader', 'text/x-c++' : 'CPP', 'text/x-c++src' : 'CPP', 'text/x-c++hdr' : 'CPPHeader', 'text/x-shellscript' : 'Shell', 'application/x-csh' : 'Shell', 'text/x-python' : 'Python', 'text/x-java' : 'Java', 'text/x-java-source' : 'Java', 'text/x-ruby' : 'Ruby', 'text/x-perl' : 'Perl', 'text/x-sql' : 'SQL', 'text/xml' : 'XML', 'text/x-comma-separated-values' : 'CSV', 'image/x-ms-bmp' : 'BMP', 'image/jpeg' : 'JPEG', 'image/gif' : 'GIF', 'image/png' : 'PNG', 'image/tiff' : 'TIFF', 'image/x-targa' : 'TGA', 'image/vnd.adobe.photoshop' : 'PSD', 'image/xbm' : 'XBITMAP', 'image/pxm' : 'PXM', 'audio/mpeg' : 'AudioMPEG', 'audio/midi' : 'AudioMIDI', 'audio/ogg' : 'AudioOGG', 'audio/mp4' : 'AudioMPEG4', 'audio/x-m4a' : 'AudioMPEG4', 'audio/wav' : 'AudioWAV', 'audio/x-mp3-playlist' : 'AudioPlaylist', 'video/x-dv' : 'VideoDV', 'video/mp4' : 'VideoMPEG4', 'video/mpeg' : 'VideoMPEG', 'video/x-msvideo' : 'VideoAVI', 'video/quicktime' : 'VideoMOV', 'video/x-ms-wmv' : 'VideoWM', 'video/x-flv' : 'VideoFlash', 'video/x-matroska' : 'VideoMKV', 'video/ogg' : 'VideoOGG' }, /** * Ajax request data validation rules * * @type Object */ rules : { defaults : function(data) { if (!data || (data.added && !$.isArray(data.added)) || (data.removed && !$.isArray(data.removed)) || (data.changed && !$.isArray(data.changed))) { return false; } return true; }, open : function(data) { return data && data.cwd && data.files && $.isPlainObject(data.cwd) && $.isArray(data.files); }, tree : function(data) { return data && data.tree && $.isArray(data.tree); }, parents : function(data) { return data && data.tree && $.isArray(data.tree); }, tmb : function(data) { return data && data.images && ($.isPlainObject(data.images) || $.isArray(data.images)); }, upload : function(data) { return data && ($.isPlainObject(data.added) || $.isArray(data.added));}, search : function(data) { return data && data.files && $.isArray(data.files)} }, /** * Commands costructors * * @type Object */ commands : {}, parseUploadData : function(text) { var data; if (!$.trim(text)) { return {error : ['errResponse', 'errDataEmpty']}; } try { data = $.parseJSON(text); } catch (e) { return {error : ['errResponse', 'errDataNotJSON']} } if (!this.validResponse('upload', data)) { return {error : ['errResponse']}; } data = this.normalize(data); data.removed = $.map(data.added||[], function(f) { return f.hash; }) return data; }, iframeCnt : 0, uploads : { // upload transport using iframe iframe : function(data, fm) { var self = fm ? fm : this, input = data.input, dfrd = $.Deferred() .fail(function(error) { error && self.error(error); }) .done(function(data) { data.warning && self.error(data.warning); data.removed && self.remove(data); data.added && self.add(data); data.changed && self.change(data); self.trigger('upload', data); data.sync && self.sync(); }), name = 'iframe-'+self.namespace+(++self.iframeCnt), form = $('
'), msie = $.browser.msie, // clear timeouts, close notification dialog, remove form/iframe onload = function() { abortto && clearTimeout(abortto); notifyto && clearTimeout(notifyto); notify && self.notify({type : 'upload', cnt : -cnt}); setTimeout(function() { msie && $('