[Pkg-owncloud-commits] [owncloud] 146/172: Namespacing for FileList, FileActions and trashbin app

David Prévot taffit at moszumanska.debian.org
Sun May 18 20:09:51 UTC 2014


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository owncloud.

commit 9d38e3602b2faf37d861729c52690ce51b8fee97
Author: Vincent Petry <pvince81 at owncloud.com>
Date:   Thu May 8 22:06:30 2014 +0200

    Namespacing for FileList, FileActions and trashbin app
    
    - FileList is now an instantiable class
    - FileActions is now in namespace
    - added App class for trashbin app
    - moved trashbin overrides into classes extending FileList
    - replaced many static calls with "this." or "self." to make the classes
      reusable/extendable
    - new URL parameter "view" to specify which view is shown, for example
      "files" or "trashbin"
    - added OC.Util.History utility class in core for handling history
    - moved URL handling/routing to OCA.Files.App
    - popstate will correctly update the current view and notify the view of
      the URL change so it can update the current dir
    - added JS unitt tests for the trashbin app
    - fixed public app to work with the new namespaces
---
 apps/files/appinfo/app.php                      |   10 +
 apps/files/css/files.css                        |   29 +-
 apps/files/index.php                            |   46 +-
 apps/files/js/app.js                            |  116 +-
 apps/files/js/breadcrumb.js                     |    2 +-
 apps/files/js/file-upload.js                    |   12 +-
 apps/files/js/fileactions.js                    |  433 ++--
 apps/files/js/filelist.js                       | 3099 ++++++++++++-----------
 apps/files/js/files.js                          |  642 +++--
 apps/files/js/filesummary.js                    |    2 +-
 apps/files/js/navigation.js                     |   38 +-
 apps/files/list.php                             |   50 +
 apps/files/templates/appnavigation.php          |    2 -
 apps/files/templates/index.php                  |  111 -
 apps/files/templates/{index.php => list.php}    |   29 +-
 apps/files/tests/js/appSpec.js                  |  169 ++
 apps/files/tests/js/breadcrumbSpec.js           |    4 +-
 apps/files/tests/js/fileactionsSpec.js          |   23 +-
 apps/files/tests/js/filelistSpec.js             |  601 ++---
 apps/files/tests/js/filesSpec.js                |   17 +-
 apps/files/tests/js/filesummarySpec.js          |    3 +-
 apps/files_sharing/css/public.css               |    4 +
 apps/files_sharing/js/public.js                 |  191 +-
 apps/files_sharing/js/share.js                  |    5 +-
 apps/files_sharing/public.php                   |    3 +-
 apps/files_trashbin/appinfo/app.php             |   14 +-
 apps/files_trashbin/index.php                   |    8 +-
 apps/files_trashbin/js/app.js                   |   78 +
 apps/files_trashbin/js/disableDefaultActions.js |    3 -
 apps/files_trashbin/js/filelist.js              |  365 +--
 apps/files_trashbin/js/files.js                 |   23 +
 apps/files_trashbin/js/trash.js                 |  130 -
 apps/files_trashbin/templates/index.php         |    2 +
 apps/files_trashbin/tests/js/appSpec.js         |   69 +
 apps/files_trashbin/tests/js/filelistSpec.js    |  219 ++
 core/js/js.js                                   |  108 +
 tests/karma.config.js                           |    2 +-
 37 files changed, 3693 insertions(+), 2969 deletions(-)

diff --git a/apps/files/appinfo/app.php b/apps/files/appinfo/app.php
index 15a2913..4b0db45 100644
--- a/apps/files/appinfo/app.php
+++ b/apps/files/appinfo/app.php
@@ -19,3 +19,13 @@ $templateManager->registerTemplate('text/html', 'core/templates/filetemplates/te
 $templateManager->registerTemplate('application/vnd.oasis.opendocument.presentation', 'core/templates/filetemplates/template.odp');
 $templateManager->registerTemplate('application/vnd.oasis.opendocument.text', 'core/templates/filetemplates/template.odt');
 $templateManager->registerTemplate('application/vnd.oasis.opendocument.spreadsheet', 'core/templates/filetemplates/template.ods');
+
+\OCA\Files\App::getNavigationManager()->add(
+	array(
+		"id" => 'files',
+		"appname" => 'files',
+		"script" => 'list.php',
+		"order" => 0,
+		"name" => $l->t('All files')
+	)
+);
diff --git a/apps/files/css/files.css b/apps/files/css/files.css
index db23f54..ba299ea 100644
--- a/apps/files/css/files.css
+++ b/apps/files/css/files.css
@@ -70,9 +70,8 @@
 
 /* FILE TABLE */
 
-#filestable {
+.app-files #filestable {
 	position: relative;
-	top: 44px;
 	width: 100%;
 }
 /* make sure there's enough room for the file actions */
@@ -84,9 +83,29 @@
 }
 
 #filestable tbody tr { background-color:#fff; height:51px; }
+/**
+ * Override global #controls styles
+ * to be more flexible / relative
+ */
 .app-files #controls {
-	left: 300px;
+	position: static;
+	left: auto;
+	top: auto;
 }
+
+.app-files #app-navigation {
+	width: 150px;
+}
+
+.app-files #app-settings {
+	width: 149px; /* DUH */
+}
+
+.app-files #app-settings input {
+	width: 90%;
+}
+
+#filestable tbody tr { background-color:#fff; height:40px; }
 #filestable tbody tr:hover, tbody tr:active {
 	background-color: rgb(240,240,240);
 }
@@ -434,7 +453,3 @@ table.dragshadow td.size {
 .mask.transparent{
 	opacity: 0;
 }
-
-.app-files #app-settings input {
-	width: 90%;
-}
diff --git a/apps/files/index.php b/apps/files/index.php
index 07c828f..69fa1c1 100644
--- a/apps/files/index.php
+++ b/apps/files/index.php
@@ -38,28 +38,23 @@ OCP\Util::addscript('files', 'breadcrumb');
 OCP\Util::addscript('files', 'filelist');
 
 OCP\App::setActiveNavigationEntry('files_index');
-// Load the files
-$dir = isset($_GET['dir']) ? stripslashes($_GET['dir']) : '';
-$dir = \OC\Files\Filesystem::normalizePath($dir);
-$dirInfo = \OC\Files\Filesystem::getFileInfo($dir, false);
-// Redirect if directory does not exist
-if (!$dirInfo || !$dirInfo->getType() === 'dir') {
-	header('Location: ' . OCP\Util::getScriptName() . '');
-	exit();
-}
 
 $isIE8 = false;
 preg_match('/MSIE (.*?);/', $_SERVER['HTTP_USER_AGENT'], $matches);
-if (count($matches) > 0 && $matches[1] <= 8){
+if (count($matches) > 0 && $matches[1] <= 9) {
 	$isIE8 = true;
 }
 
-// if IE8 and "?dir=path" was specified, reformat the URL to use a hash like "#?dir=path"
-if ($isIE8 && isset($_GET['dir'])){
-	if ($dir === ''){
-		$dir = '/';
+// if IE8 and "?dir=path&view=someview" was specified, reformat the URL to use a hash like "#?dir=path&view=someview"
+if ($isIE8 && (isset($_GET['dir']) || isset($_GET['view']))) {
+	$hash = '#?';
+	$dir = isset($_GET['dir']) ? $_GET['dir'] : '/';
+	$view = isset($_GET['view']) ? $_GET['view'] : 'files';
+	$hash = '#?dir=' . \OCP\Util::encodePath($dir);
+	if ($view !== 'files') {
+		$hash .= '&view=' . urlencode($view);
 	}
-	header('Location: ' . OCP\Util::linkTo('files', 'index.php') . '#?dir=' . \OCP\Util::encodePath($dir));
+	header('Location: ' . OCP\Util::linkTo('files', 'index.php') . $hash);
 	exit();
 }
 
@@ -67,16 +62,6 @@ $user = OC_User::getUser();
 
 $config = \OC::$server->getConfig();
 
-// needed for share init, permissions will be reloaded
-// anyway with ajax load
-$permissions = $dirInfo->getPermissions();
-
-// information about storage capacities
-$storageInfo=OC_Helper::getStorageInfo($dir, $dirInfo);
-$freeSpace=$storageInfo['free'];
-$uploadLimit=OCP\Util::uploadLimit();
-$maxUploadFilesize=OCP\Util::maxUploadFilesize($dir, $freeSpace);
-$publicUploadEnabled = $config->getAppValue('core', 'shareapi_allow_public_upload', 'yes');
 // if the encryption app is disabled, than everything is fine (INIT_SUCCESSFUL status code)
 $encryptionInitStatus = 2;
 if (OC_App::isEnabled('files_encryption')) {
@@ -105,6 +90,7 @@ function renderScript($appName, $scriptName) {
 	return $content;
 }
 
+// render the container content for every navigation item
 foreach ($navItems as $item) {
 	$content = '';
 	if (isset($item['script'])) {
@@ -121,21 +107,11 @@ OCP\Util::addscript('files', 'files');
 OCP\Util::addscript('files', 'navigation');
 OCP\Util::addscript('files', 'keyboardshortcuts');
 $tmpl = new OCP\Template('files', 'index', 'user');
-$tmpl->assign('dir', $dir);
-$tmpl->assign('permissions', $permissions);
-$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); // minimium of freeSpace and uploadLimit
-$tmpl->assign('uploadMaxHumanFilesize', OCP\Util::humanFileSize($maxUploadFilesize));
-$tmpl->assign('freeSpace', $freeSpace);
-$tmpl->assign('uploadLimit', $uploadLimit); // PHP upload limit
-$tmpl->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true)));
-$tmpl->assign('usedSpacePercent', (int)$storageInfo['relative']);
 $tmpl->assign('isPublic', false);
-$tmpl->assign('publicUploadEnabled', $publicUploadEnabled);
 $tmpl->assign("encryptedFiles", \OCP\Util::encryptedFiles());
 $tmpl->assign("mailNotificationEnabled", $config->getAppValue('core', 'shareapi_allow_mail_notification', 'yes'));
 $tmpl->assign("allowShareWithLink", $config->getAppValue('core', 'shareapi_allow_links', 'yes'));
 $tmpl->assign("encryptionInitStatus", $encryptionInitStatus);
-$tmpl->assign('disableSharing', false);
 $tmpl->assign('appNavigation', $nav);
 $tmpl->assign('appContents', $contentItems);
 
diff --git a/apps/files/js/app.js b/apps/files/js/app.js
index 87f7e2b..6cdb339 100644
--- a/apps/files/js/app.js
+++ b/apps/files/js/app.js
@@ -11,15 +11,117 @@
  *
  */
 
-if (!OCA.Files) {
-	OCA.Files = {};
-}
+(function() {
 
-$(document).ready(function() {
-	var nav = new OCA.Files.Navigation($('#app-navigation ul'));
+	if (!OCA.Files) {
+		OCA.Files = {};
+	}
+
+	var App = {
+		navigation: null,
+
+		initialize: function() {
+			this.navigation = new OCA.Files.Navigation($('#app-navigation'));
+
+			// TODO: ideally these should be in a separate class / app (the embedded "all files" app)
+			this.fileList = OCA.Files.FileList;
+			this.fileActions = OCA.Files.FileActions;
+			this.files = OCA.Files.Files;
+
+			this.fileList = new OCA.Files.FileList($('#app-content-files'));
+			this.files.initialize();
+			this.fileActions.registerDefaultActions(this.fileList);
+			this.fileList.setFileActions(this.fileActions);
+
+			// for backward compatibility, the global FileList will
+			// refer to the one of the "files" view
+			window.FileList = this.fileList;
+
+			this._setupEvents();
+			// trigger URL change event handlers
+			this._onPopState(OC.Util.History.parseUrlQuery());
+		},
+
+		/**
+		 * Returns the container of the currently visible app.
+		 *
+		 * @return app container
+		 */
+		getCurrentAppContainer: function() {
+			return this.navigation.getActiveContainer();
+		},
 
-	nav.setSelectedItem('files');
+		/**
+		 * Setup events based on URL changes
+		 */
+		_setupEvents: function() {
+			OC.Util.History.addOnPopStateHandler(_.bind(this._onPopState, this));
 
-	// TODO: init file list, actions and others
+			// detect when app changed their current directory
+			$('#app-content>div').on('changeDirectory', _.bind(this._onDirectoryChanged, this));
+
+			$('#app-navigation').on('itemChanged', _.bind(this._onNavigationChanged, this));
+		},
+
+		/**
+		 * Event handler for when the current navigation item has changed
+		 */
+		_onNavigationChanged: function(e) {
+			var params;
+			if (e && e.itemId) {
+				params = {
+					view: e.itemId,
+					dir: '/'
+				};
+				this._changeUrl(params.view, params.dir);
+				this.navigation.getActiveContainer().trigger(new $.Event('urlChanged', params));
+			}
+		},
+
+		/**
+		 * Event handler for when an app notified that its directory changed
+		 */
+		_onDirectoryChanged: function(e) {
+			if (e.dir) {
+				this._changeUrl(this.navigation.getActiveItem(), e.dir);
+			}
+		},
+
+		/**
+		 * Event handler for when the URL changed
+		 */
+		_onPopState: function(params) {
+			params = _.extend({
+				dir: '/',
+				view: 'files'
+			}, params);
+			var lastId = this.navigation.getActiveItem();
+			this.navigation.setActiveItem(params.view, {silent: true});
+			if (lastId !== this.navigation.getActiveItem()) {
+				this.navigation.getActiveContainer().trigger(new $.Event('show'));
+			}
+			this.navigation.getActiveContainer().trigger(new $.Event('urlChanged', params));
+		},
+
+		/**
+		 * Change the URL to point to the given dir and view
+		 */
+		_changeUrl: function(view, dir) {
+			var params = {dir: dir};
+			if (view !== 'files') {
+				params.view = view;
+			}
+			OC.Util.History.pushState(params);
+		}
+	};
+	OCA.Files.App = App;
+})();
+
+$(document).ready(function() {
+	// wait for other apps/extensions to register their event handlers
+	// in the "ready" clause
+	_.defer(function() {
+		OCA.Files.App.initialize();
+	});
 });
 
diff --git a/apps/files/js/breadcrumb.js b/apps/files/js/breadcrumb.js
index 5bc2fac..1a77481 100644
--- a/apps/files/js/breadcrumb.js
+++ b/apps/files/js/breadcrumb.js
@@ -236,6 +236,6 @@
 		}
 	};
 
-	window.BreadCrumb = BreadCrumb;
+	OCA.Files.BreadCrumb = BreadCrumb;
 })();
 
diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js
index 67775b2..0ca2852 100644
--- a/apps/files/js/file-upload.js
+++ b/apps/files/js/file-upload.js
@@ -346,7 +346,7 @@ OC.Upload = {
 						// noone set update parameters, we set the minimum
 						data.formData = {
 							requesttoken: oc_requesttoken,
-							dir: $('#dir').val(),
+							dir: FileList.getCurrentDirectory(),
 							file_directory: fileDirectory
 						};
 					}
@@ -595,7 +595,7 @@ OC.Upload = {
 					if (FileList.lastAction) {
 						FileList.lastAction();
 					}
-					var name = getUniqueName(newname);
+					var name = FileList.getUniqueName(newname);
 					if (newname !== name) {
 						FileList.checkName(name, newname, true);
 						var hidden = true;
@@ -607,7 +607,7 @@ OC.Upload = {
 							$.post(
 								OC.filePath('files', 'ajax', 'newfile.php'),
 								{
-									dir: $('#dir').val(),
+									dir: FileList.getCurrentDirectory(),
 									filename: name
 								},
 								function(result) {
@@ -623,7 +623,7 @@ OC.Upload = {
 							$.post(
 								OC.filePath('files','ajax','newfolder.php'),
 								{
-									dir: $('#dir').val(),
+									dir: FileList.getCurrentDirectory(),
 									foldername: name
 								},
 								function(result) {
@@ -648,7 +648,7 @@ OC.Upload = {
 							} else { //or the domain
 								localName = (localName.match(/:\/\/(.[^\/]+)/)[1]).replace('www.', '');
 							}
-							localName = getUniqueName(localName);
+							localName = FileList.getUniqueName(localName);
 							//IE < 10 does not fire the necessary events for the progress bar.
 							if ($('html.lte9').length === 0) {
 								$('#uploadprogressbar').progressbar({value: 0});
@@ -658,7 +658,7 @@ OC.Upload = {
 							var eventSource = new OC.EventSource(
 								OC.filePath('files', 'ajax', 'newfile.php'),
 								{
-									dir: $('#dir').val(),
+									dir: FileList.getCurrentDirectory(),
 									source: name,
 									filename: localName
 								}
diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js
index ecdfa72..7be5998 100644
--- a/apps/files/js/fileactions.js
+++ b/apps/files/js/fileactions.js
@@ -8,242 +8,253 @@
  *
  */
 
-/* global OC, FileList, Files */
 /* global trashBinApp */
-var FileActions = {
-	actions: {},
-	defaults: {},
-	icons: {},
-	currentFile: null,
-	register: function (mime, name, permissions, icon, action, displayName) {
-		if (!FileActions.actions[mime]) {
-			FileActions.actions[mime] = {};
-		}
-		if (!FileActions.actions[mime][name]) {
-			FileActions.actions[mime][name] = {};
-		}
-		if (!displayName) {
-			displayName = t('files', name);
-		}
-		FileActions.actions[mime][name]['action'] = action;
-		FileActions.actions[mime][name]['permissions'] = permissions;
-		FileActions.actions[mime][name]['displayName'] = displayName;
-		FileActions.icons[name] = icon;
-	},
-	setDefault: function (mime, name) {
-		FileActions.defaults[mime] = name;
-	},
-	get: function (mime, type, permissions) {
-		var actions = this.getActions(mime, type, permissions);
-		var filteredActions = {};
-		$.each(actions, function (name, action) {
-			filteredActions[name] = action.action;
-		});
-		return filteredActions;
-	},
-	getActions: function (mime, type, permissions) {
-		var actions = {};
-		if (FileActions.actions.all) {
-			actions = $.extend(actions, FileActions.actions.all);
-		}
-		if (type) {//type is 'dir' or 'file'
-			if (FileActions.actions[type]) {
-				actions = $.extend(actions, FileActions.actions[type]);
+(function() {
+
+	var FileActions = {
+		actions: {},
+		defaults: {},
+		icons: {},
+		currentFile: null,
+		register: function (mime, name, permissions, icon, action, displayName) {
+			if (!this.actions[mime]) {
+				this.actions[mime] = {};
 			}
-		}
-		if (mime) {
-			var mimePart = mime.substr(0, mime.indexOf('/'));
-			if (FileActions.actions[mimePart]) {
-				actions = $.extend(actions, FileActions.actions[mimePart]);
+			if (!this.actions[mime][name]) {
+				this.actions[mime][name] = {};
 			}
-			if (FileActions.actions[mime]) {
-				actions = $.extend(actions, FileActions.actions[mime]);
+			if (!displayName) {
+				displayName = t('files', name);
 			}
-		}
-		var filteredActions = {};
-		$.each(actions, function (name, action) {
-			if (action.permissions & permissions) {
-				filteredActions[name] = action;
+			this.actions[mime][name]['action'] = action;
+			this.actions[mime][name]['permissions'] = permissions;
+			this.actions[mime][name]['displayName'] = displayName;
+			this.icons[name] = icon;
+		},
+		clear: function() {
+			this.actions = {};
+			this.defaults = {};
+			this.icons = {};
+			this.currentFile = null;
+		},
+		setDefault: function (mime, name) {
+			this.defaults[mime] = name;
+		},
+		get: function (mime, type, permissions) {
+			var actions = this.getActions(mime, type, permissions);
+			var filteredActions = {};
+			$.each(actions, function (name, action) {
+				filteredActions[name] = action.action;
+			});
+			return filteredActions;
+		},
+		getActions: function (mime, type, permissions) {
+			var actions = {};
+			if (this.actions.all) {
+				actions = $.extend(actions, this.actions.all);
 			}
-		});
-		return filteredActions;
-	},
-	getDefault: function (mime, type, permissions) {
-		var mimePart;
-		if (mime) {
-			mimePart = mime.substr(0, mime.indexOf('/'));
-		}
-		var name = false;
-		if (mime && FileActions.defaults[mime]) {
-			name = FileActions.defaults[mime];
-		} else if (mime && FileActions.defaults[mimePart]) {
-			name = FileActions.defaults[mimePart];
-		} else if (type && FileActions.defaults[type]) {
-			name = FileActions.defaults[type];
-		} else {
-			name = FileActions.defaults.all;
-		}
-		var actions = this.get(mime, type, permissions);
-		return actions[name];
-	},
-	/**
-	 * Display file actions for the given element
-	 * @param parent "td" element of the file for which to display actions
-	 * @param triggerEvent if true, triggers the fileActionsReady on the file
-	 * list afterwards (false by default)
-	 */
-	display: function (parent, triggerEvent) {
-		FileActions.currentFile = parent;
-		var actions = FileActions.getActions(FileActions.getCurrentMimeType(), FileActions.getCurrentType(), FileActions.getCurrentPermissions());
-		var file = FileActions.getCurrentFile();
-		var nameLinks;
-		if (FileList.findFileEl(file).data('renaming')) {
-			return;
-		}
+			if (type) {//type is 'dir' or 'file'
+				if (this.actions[type]) {
+					actions = $.extend(actions, this.actions[type]);
+				}
+			}
+			if (mime) {
+				var mimePart = mime.substr(0, mime.indexOf('/'));
+				if (this.actions[mimePart]) {
+					actions = $.extend(actions, this.actions[mimePart]);
+				}
+				if (this.actions[mime]) {
+					actions = $.extend(actions, this.actions[mime]);
+				}
+			}
+			var filteredActions = {};
+			$.each(actions, function (name, action) {
+				if (action.permissions & permissions) {
+					filteredActions[name] = action;
+				}
+			});
+			return filteredActions;
+		},
+		getDefault: function (mime, type, permissions) {
+			var mimePart;
+			if (mime) {
+				mimePart = mime.substr(0, mime.indexOf('/'));
+			}
+			var name = false;
+			if (mime && this.defaults[mime]) {
+				name = this.defaults[mime];
+			} else if (mime && this.defaults[mimePart]) {
+				name = this.defaults[mimePart];
+			} else if (type && this.defaults[type]) {
+				name = this.defaults[type];
+			} else {
+				name = this.defaults.all;
+			}
+			var actions = this.get(mime, type, permissions);
+			return actions[name];
+		},
+		/**
+		 * Display file actions for the given element
+		 * @param parent "td" element of the file for which to display actions
+		 * @param triggerEvent if true, triggers the fileActionsReady on the file
+		 * list afterwards (false by default)
+		 */
+		display: function (parent, triggerEvent) {
+			this.currentFile = parent;
+			var self = this;
+			var actions = this.getActions(this.getCurrentMimeType(), this.getCurrentType(), this.getCurrentPermissions());
+			var file = this.getCurrentFile();
+			var nameLinks;
+			if (parent.closest('tr').data('renaming')) {
+				return;
+			}
+
+			// recreate fileactions
+			nameLinks = parent.children('a.name');
+			nameLinks.find('.fileactions, .nametext .action').remove();
+			nameLinks.append('<span class="fileactions" />');
+			var defaultAction = this.getDefault(this.getCurrentMimeType(), this.getCurrentType(), this.getCurrentPermissions());
 
-		// recreate fileactions
-		nameLinks = parent.children('a.name');
-		nameLinks.find('.fileactions, .nametext .action').remove();
-		nameLinks.append('<span class="fileactions" />');
-		var defaultAction = FileActions.getDefault(FileActions.getCurrentMimeType(), FileActions.getCurrentType(), FileActions.getCurrentPermissions());
+			var actionHandler = function (event) {
+				event.stopPropagation();
+				event.preventDefault();
 
-		var actionHandler = function (event) {
-			event.stopPropagation();
-			event.preventDefault();
+				self.currentFile = event.data.elem;
+				var file = self.getCurrentFile();
 
-			FileActions.currentFile = event.data.elem;
-			var file = FileActions.getCurrentFile();
+				event.data.actionFunc(file);
+			};
 
-			event.data.actionFunc(file);
-		};
+			var addAction = function (name, action, displayName) {
 
-		var addAction = function (name, action, displayName) {
+				if ((name === 'Download' || action !== defaultAction) && name !== 'Delete') {
 
-			if ((name === 'Download' || action !== defaultAction) && name !== 'Delete') {
+					var img = self.icons[name],
+						actionText = displayName,
+						actionContainer = 'a.name>span.fileactions';
 
-				var img = FileActions.icons[name],
-					actionText = displayName,
-					actionContainer = 'a.name>span.fileactions';
+					if (name === 'Rename') {
+						// rename has only an icon which appears behind
+						// the file name
+						actionText = '';
+						actionContainer = 'a.name span.nametext';
+					}
+					if (img.call) {
+						img = img(file);
+					}
+					var html = '<a href="#" class="action action-' + name.toLowerCase() + '" data-action="' + name + '">';
+					if (img) {
+						html += '<img class ="svg" src="' + img + '" />';
+					}
+					html += '<span> ' + actionText + '</span></a>';
 
-				if (name === 'Rename') {
-					// rename has only an icon which appears behind
-					// the file name
-					actionText = '';
-					actionContainer = 'a.name span.nametext';
+					var element = $(html);
+					element.data('action', name);
+					element.on('click', {a: null, elem: parent, actionFunc: actions[name].action}, actionHandler);
+					parent.find(actionContainer).append(element);
 				}
+
+			};
+
+			$.each(actions, function (name, action) {
+				if (name !== 'Share') {
+					displayName = action.displayName;
+					ah = action.action;
+
+					addAction(name, ah, displayName);
+				}
+			});
+			if(actions.Share){
+				displayName = t('files', 'Share');
+				addAction('Share', actions.Share, displayName);
+			}
+
+			// remove the existing delete action
+			parent.parent().children().last().find('.action.delete').remove();
+			if (actions['Delete']) {
+				var img = self.icons['Delete'];
+				var html;
 				if (img.call) {
 					img = img(file);
 				}
-				var html = '<a href="#" class="action action-' + name.toLowerCase() + '" data-action="' + name + '">';
-				if (img) {
-					html += '<img class ="svg" src="' + img + '" />';
+				if (typeof trashBinApp !== 'undefined' && trashBinApp) {
+					html = '<a href="#" original-title="' + t('files', 'Delete permanently') + '" class="action delete delete-icon" />';
+				} else {
+					html = '<a href="#" class="action delete delete-icon" />';
 				}
-				html += '<span> ' + actionText + '</span></a>';
-
 				var element = $(html);
-				element.data('action', name);
-				element.on('click', {a: null, elem: parent, actionFunc: actions[name].action}, actionHandler);
-				parent.find(actionContainer).append(element);
+				element.data('action', actions['Delete']);
+				element.on('click', {a: null, elem: parent, actionFunc: actions['Delete'].action}, actionHandler);
+				parent.parent().children().last().append(element);
 			}
 
-		};
+			if (triggerEvent){
+				$('#fileList').trigger(jQuery.Event("fileActionsReady"));
+			}
+		},
+		getCurrentFile: function () {
+			return this.currentFile.parent().attr('data-file');
+		},
+		getCurrentMimeType: function () {
+			return this.currentFile.parent().attr('data-mime');
+		},
+		getCurrentType: function () {
+			return this.currentFile.parent().attr('data-type');
+		},
+		getCurrentPermissions: function () {
+			return this.currentFile.parent().data('permissions');
+		},
 
-		$.each(actions, function (name, action) {
-			if (name !== 'Share') {
-				displayName = action.displayName;
-				ah = action.action;
+		/**
+		 * Register the actions that are used by default for the files app.
+		 */
+		registerDefaultActions: function(fileList) {
+			this.register('all', 'Delete', OC.PERMISSION_DELETE, function () {
+				return OC.imagePath('core', 'actions/delete');
+			}, function (filename) {
+				fileList.do_delete(filename);
+				$('.tipsy').remove();
+			});
 
-				addAction(name, ah, displayName);
-			}
-		});
-		if(actions.Share){
-			displayName = t('files', 'Share');
-			addAction('Share', actions.Share, displayName);
-		}
+			// t('files', 'Rename')
+			this.register('all', 'Rename', OC.PERMISSION_UPDATE, function () {
+				return OC.imagePath('core', 'actions/rename');
+			}, function (filename) {
+				fileList.rename(filename);
+			});
 
-		// remove the existing delete action
-		parent.parent().children().last().find('.action.delete').remove();
-		if (actions['Delete']) {
-			var img = FileActions.icons['Delete'];
-			var html;
-			if (img.call) {
-				img = img(file);
-			}
-			if (typeof trashBinApp !== 'undefined' && trashBinApp) {
-				html = '<a href="#" original-title="' + t('files', 'Delete permanently') + '" class="action delete delete-icon" />';
+			this.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename) {
+				var dir = fileList.getCurrentDirectory();
+				if (dir !== '/') {
+					dir = dir + '/';
+				}
+				fileList.changeDirectory(dir + filename);
+			});
+
+			this.setDefault('dir', 'Open');
+			var downloadScope;
+			if ($('#allowZipDownload').val() == 1) {
+				downloadScope = 'all';
 			} else {
-				html = '<a href="#" class="action delete delete-icon" />';
+				downloadScope = 'file';
 			}
-			var element = $(html);
-			element.data('action', actions['Delete']);
-			element.on('click', {a: null, elem: parent, actionFunc: actions['Delete'].action}, actionHandler);
-			parent.parent().children().last().append(element);
-		}
 
-		if (triggerEvent){
-			$('#fileList').trigger(jQuery.Event("fileActionsReady"));
+			this.register(downloadScope, 'Download', OC.PERMISSION_READ, function () {
+				return OC.imagePath('core', 'actions/download');
+			}, function (filename) {
+				var url = OCA.Files.Files.getDownloadUrl(filename, fileList.getCurrentDirectory());
+				if (url) {
+					OC.redirect(url);
+				}
+			});
+
+			fileList.$fileList.trigger(jQuery.Event("fileActionsReady"));
 		}
-	},
-	getCurrentFile: function () {
-		return FileActions.currentFile.parent().attr('data-file');
-	},
-	getCurrentMimeType: function () {
-		return FileActions.currentFile.parent().attr('data-mime');
-	},
-	getCurrentType: function () {
-		return FileActions.currentFile.parent().attr('data-type');
-	},
-	getCurrentPermissions: function () {
-		return FileActions.currentFile.parent().data('permissions');
-	}
-};
-
-$(document).ready(function () {
-	var downloadScope;
-	if ($('#allowZipDownload').val() == 1) {
-		downloadScope = 'all';
-	} else {
-		downloadScope = 'file';
-	}
-
-	if (typeof disableDownloadActions == 'undefined' || !disableDownloadActions) {
-		FileActions.register(downloadScope, 'Download', OC.PERMISSION_READ, function () {
-			return OC.imagePath('core', 'actions/download');
-		}, function (filename) {
-			var url = Files.getDownloadUrl(filename);
-			if (url) {
-				OC.redirect(url);
-			}
-		});
-	}
-	$('#fileList tr').each(function () {
-		FileActions.display($(this).children('td.filename'));
-	});
-
-	$('#fileList').trigger(jQuery.Event("fileActionsReady"));
-
-});
-
-FileActions.register('all', 'Delete', OC.PERMISSION_DELETE, function () {
-	return OC.imagePath('core', 'actions/delete');
-}, function (filename) {
-	FileList.do_delete(filename);
-	$('.tipsy').remove();
-});
-
-// t('files', 'Rename')
-FileActions.register('all', 'Rename', OC.PERMISSION_UPDATE, function () {
-	return OC.imagePath('core', 'actions/rename');
-}, function (filename) {
-	FileList.rename(filename);
-});
-
-FileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename) {
-	var dir = $('#dir').val() || '/';
-	if (dir !== '/') {
-		dir = dir + '/';
-	}
-	FileList.changeDirectory(dir + filename);
-});
-
-FileActions.setDefault('dir', 'Open');
+	};
+
+	OCA.Files.FileActions = FileActions;
+})();
+
+// for backward compatibility
+window.FileActions = OCA.Files.FileActions;
+
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 73a4413..de34d93 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -8,1639 +8,1730 @@
  *
  */
 
-/* global OC, t, n, FileList, FileActions, Files, FileSummary, BreadCrumb */
+/* global Files */
 /* global dragOptions, folderDropOptions */
-window.FileList = {
-	SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s',
-	SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n',
-
-	appName: t('files', 'Files'),
-	isEmpty: true,
-	useUndo:true,
-	$el: $('#filestable'),
-	$fileList: $('#fileList'),
-	breadcrumb: null,
-
-	/**
-	 * Instance of FileSummary
-	 */
-	fileSummary: null,
-	initialized: false,
-
-	// number of files per page
-	pageSize: 20,
-
+(function() {
 	/**
-	 * Array of files in the current folder.
-	 * The entries are of file data.
+	 * The FileList class manages a file list view.
+	 * A file list view consists of a controls bar and
+	 * a file list table.
 	 */
-	files: [],
-
-	/**
-	 * Map of file id to file data
-	 */
-	_selectedFiles: {},
-
-	/**
-	 * Summary of selected files.
-	 * Instance of FileSummary.
-	 */
-	_selectionSummary: null,
+	var FileList = function($el) {
+		this.initialize($el);
+	};
+	FileList.prototype = {
+		SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s',
+		SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n',
+
+		appName: t('files', 'Files'),
+		isEmpty: true,
+		useUndo:true,
+
+		/**
+		 * Top-level container with controls and file list
+		 */
+		$el: null,
+
+		/**
+		 * Files table
+		 */
+		$table: null,
+
+		/**
+		 * List of rows (table tbody)
+		 */
+		$fileList: null,
+
+		breadcrumb: null,
+
+		/**
+		 * Instance of FileSummary
+		 */
+		fileSummary: null,
+		initialized: false,
+
+		// number of files per page
+		pageSize: 20,
+
+		/**
+		 * Array of files in the current folder.
+		 * The entries are of file data.
+		 */
+		files: [],
+
+		/**
+		 * File actions handler, defaults to OCA.Files.FileActions
+		 */
+		fileActions: null,
+
+		/**
+		 * Map of file id to file data
+		 */
+		_selectedFiles: {},
+
+		/**
+		 * Summary of selected files.
+		 * Instance of FileSummary.
+		 */
+		_selectionSummary: null,
+
+		/**
+		 * Sort attribute
+		 */
+		_sort: 'name',
+
+		/**
+		 * Sort direction: 'asc' or 'desc'
+		 */
+		_sortDirection: 'asc',
+
+		/**
+		 * Sort comparator function for the current sort
+		 */
+		_sortComparator: null,
+
+		/**
+		 * Current directory
+		 */
+		_currentDirectory: null,
+
+		/**
+		 * Initialize the file list and its components
+		 */
+		initialize: function($el) {
+			var self = this;
+			if (this.initialized) {
+				return;
+			}
 
-	/**
-	 * Sort attribute
-	 */
-	_sort: 'name',
+			this.$el = $el;
+			this.$table = $el.find('table:first');
+			this.$fileList = $el.find('#fileList');
+			this.fileActions = OCA.Files.FileActions;
+			this.files = [];
+			this._selectedFiles = {};
+			this._selectionSummary = new OCA.Files.FileSummary();
 
-	/**
-	 * Sort direction: 'asc' or 'desc'
-	 */
-	_sortDirection: 'asc',
+			this.fileSummary = this._createSummary();
 
-	/**
-	 * Sort comparator function for the current sort
-	 */
-	_sortComparator: null,
+			this.setSort('name', 'asc');
 
-	/**
-	 * Initialize the file list and its components
-	 */
-	initialize: function() {
-		var self = this;
-		if (this.initialized) {
-			return;
-		}
+			this.breadcrumb = new OCA.Files.BreadCrumb({
+				onClick: _.bind(this._onClickBreadCrumb, this),
+				onDrop: _.bind(this._onDropOnBreadCrumb, this),
+				getCrumbUrl: function(part, index) {
+					return self.linkTo(part.dir);
+				}
+			});
 
-		// TODO: FileList should not know about global elements
-		this.$el = $('#filestable');
-		this.$fileList = $('#fileList');
-		this.files = [];
-		this._selectedFiles = {};
-		this._selectionSummary = new FileSummary();
+			this.$el.find('#controls').prepend(this.breadcrumb.$el);
 
-		this.fileSummary = this._createSummary();
+			this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this));
 
-		this.setSort('name', 'asc');
+			$(window).resize(function() {
+				// TODO: debounce this ?
+				var width = $(this).width();
+				self.breadcrumb.resize(width, false);
+			});
 
-		this.breadcrumb = new BreadCrumb({
-			onClick: this._onClickBreadCrumb,
-			onDrop: _.bind(this._onDropOnBreadCrumb, this),
-			getCrumbUrl: function(part, index) {
-				return self.linkTo(part.dir);
+			this.$fileList.on('click','td.filename>a.name', _.bind(this._onClickFile, this));
+			this.$fileList.on('change', 'td.filename>input:checkbox', _.bind(this._onClickFileCheckbox, this));
+			this.$el.on('urlChanged', _.bind(this._onUrlChanged, this));
+			this.$el.find('#select_all').click(_.bind(this._onClickSelectAll, this));
+			this.$el.find('.download').click(_.bind(this._onClickDownloadSelected, this));
+			this.$el.find('.delete-selected').click(_.bind(this._onClickDeleteSelected, this));
+
+			this.setupUploadEvents();
+
+			// FIXME: only do this when visible
+			$(window).scroll(function(e) {self._onScroll(e);});
+		},
+
+		/**
+		 * Event handler for when the URL changed
+		 */
+		_onUrlChanged: function(e) {
+			if (e && e.dir) {
+				this.changeDirectory(e.dir, false, true);
 			}
-		});
-
-		$('#controls').prepend(this.breadcrumb.$el);
-
-		this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this));
-
-		$(window).resize(function() {
-			// TODO: debounce this ?
-			var width = $(this).width();
-			FileList.breadcrumb.resize(width, false);
-		});
-
-		this.$fileList.on('click','td.filename>a.name', _.bind(this._onClickFile, this));
-		this.$fileList.on('change', 'td.filename>input:checkbox', _.bind(this._onClickFileCheckbox, this));
-		this.$el.find('#select_all').click(_.bind(this._onClickSelectAll, this));
-		this.$el.find('.download').click(_.bind(this._onClickDownloadSelected, this));
-		this.$el.find('.delete-selected').click(_.bind(this._onClickDeleteSelected, this));
-	},
-
-	/**
-	 * Selected/deselects the given file element and updated
-	 * the internal selection cache.
-	 *
-	 * @param $tr single file row element
-	 * @param state true to select, false to deselect
-	 */
-	_selectFileEl: function($tr, state) {
-		var $checkbox = $tr.find('td.filename>input:checkbox');
-		var oldData = !!this._selectedFiles[$tr.data('id')];
-		var data;
-		$checkbox.prop('checked', state);
-		$tr.toggleClass('selected', state);
-		// already selected ?
-		if (state === oldData) {
-			return;
-		}
-		data = this.elementToFile($tr);
-		if (state) {
-			this._selectedFiles[$tr.data('id')] = data;
-			this._selectionSummary.add(data);
-		}
-		else {
-			delete this._selectedFiles[$tr.data('id')];
-			this._selectionSummary.remove(data);
-		}
-		this.$el.find('#select_all').prop('checked', this._selectionSummary.getTotal() === this.files.length);
-	},
+		},
+
+		/**
+		 * Selected/deselects the given file element and updated
+		 * the internal selection cache.
+		 *
+		 * @param $tr single file row element
+		 * @param state true to select, false to deselect
+		 */
+		_selectFileEl: function($tr, state) {
+			var $checkbox = $tr.find('td.filename>input:checkbox');
+			var oldData = !!this._selectedFiles[$tr.data('id')];
+			var data;
+			$checkbox.prop('checked', state);
+			$tr.toggleClass('selected', state);
+			// already selected ?
+			if (state === oldData) {
+				return;
+			}
+			data = this.elementToFile($tr);
+			if (state) {
+				this._selectedFiles[$tr.data('id')] = data;
+				this._selectionSummary.add(data);
+			}
+			else {
+				delete this._selectedFiles[$tr.data('id')];
+				this._selectionSummary.remove(data);
+			}
+			this.$el.find('#select_all').prop('checked', this._selectionSummary.getTotal() === this.files.length);
+		},
+
+		/**
+		 * Event handler for when clicking on files to select them
+		 */
+		_onClickFile: function(event) {
+			var $tr = $(event.target).closest('tr');
+			if (event.ctrlKey || event.shiftKey) {
+				event.preventDefault();
+				if (event.shiftKey) {
+					var $lastTr = $(this._lastChecked);
+					var lastIndex = $lastTr.index();
+					var currentIndex = $tr.index();
+					var $rows = this.$fileList.children('tr');
+
+					// last clicked checkbox below current one ?
+					if (lastIndex > currentIndex) {
+						var aux = lastIndex;
+						lastIndex = currentIndex;
+						currentIndex = aux;
+					}
 
-	/**
-	 * Event handler for when clicking on files to select them
-	 */
-	_onClickFile: function(event) {
-		var $tr = $(event.target).closest('tr');
-		if (event.ctrlKey || event.shiftKey) {
-			event.preventDefault();
-			if (event.shiftKey) {
-				var $lastTr = $(this._lastChecked);
-				var lastIndex = $lastTr.index();
-				var currentIndex = $tr.index();
-				var $rows = this.$fileList.children('tr');
-
-				// last clicked checkbox below current one ?
-				if (lastIndex > currentIndex) {
-					var aux = lastIndex;
-					lastIndex = currentIndex;
-					currentIndex = aux;
+					// auto-select everything in-between
+					for (var i = lastIndex + 1; i < currentIndex; i++) {
+						this._selectFileEl($rows.eq(i), true);
+					}
 				}
-
-				// auto-select everything in-between
-				for (var i = lastIndex + 1; i < currentIndex; i++) {
-					this._selectFileEl($rows.eq(i), true);
+				else {
+					this._lastChecked = $tr;
+				}
+				var $checkbox = $tr.find('td.filename>input:checkbox');
+				this._selectFileEl($tr, !$checkbox.prop('checked'));
+				this.updateSelectionSummary();
+			} else {
+				var filename = $tr.attr('data-file');
+				var renaming = $tr.data('renaming');
+				if (!renaming) {
+					this.fileActions.currentFile = $tr.find('td');
+					var mime = this.fileActions.getCurrentMimeType();
+					var type = this.fileActions.getCurrentType();
+					var permissions = this.fileActions.getCurrentPermissions();
+					var action = this.fileActions.getDefault(mime,type, permissions);
+					if (action) {
+						event.preventDefault();
+						action(filename);
+					}
 				}
 			}
-			else {
-				this._lastChecked = $tr;
+		},
+
+		/**
+		 * Event handler for when clicking on a file's checkbox
+		 */
+		_onClickFileCheckbox: function(e) {
+			var $tr = $(e.target).closest('tr');
+			this._selectFileEl($tr, !$tr.hasClass('selected'));
+			this._lastChecked = $tr;
+			this.updateSelectionSummary();
+		},
+
+		/**
+		 * Event handler for when selecting/deselecting all files
+		 */
+		_onClickSelectAll: function(e) {
+			var checked = $(e.target).prop('checked');
+			this.$fileList.find('td.filename>input:checkbox').prop('checked', checked)
+				.closest('tr').toggleClass('selected', checked);
+			this._selectedFiles = {};
+			this._selectionSummary.clear();
+			if (checked) {
+				for (var i = 0; i < this.files.length; i++) {
+					var fileData = this.files[i];
+					this._selectedFiles[fileData.id] = fileData;
+					this._selectionSummary.add(fileData);
+				}
 			}
-			var $checkbox = $tr.find('td.filename>input:checkbox');
-			this._selectFileEl($tr, !$checkbox.prop('checked'));
 			this.updateSelectionSummary();
-		} else {
-			var filename = $tr.attr('data-file');
-			var renaming = $tr.data('renaming');
-			if (!renaming) {
-				FileActions.currentFile = $tr.find('td');
-				var mime=FileActions.getCurrentMimeType();
-				var type=FileActions.getCurrentType();
-				var permissions = FileActions.getCurrentPermissions();
-				var action=FileActions.getDefault(mime,type, permissions);
-				if (action) {
-					event.preventDefault();
-					action(filename);
+		},
+
+		/**
+		 * Event handler for when clicking on "Download" for the selected files
+		 */
+		_onClickDownloadSelected: function(event) {
+			var files;
+			var dir = this.getCurrentDirectory();
+			if (this.isAllSelected()) {
+				files = OC.basename(dir);
+				dir = OC.dirname(dir) || '/';
+			}
+			else {
+				files = _.pluck(this.getSelectedFiles(), 'name');
+			}
+			OC.Notification.show(t('files','Your download is being prepared. This might take some time if the files are big.'));
+			OC.redirect(this.getDownloadUrl(files, dir));
+			return false;
+		},
+
+		/**
+		 * Event handler for when clicking on "Delete" for the selected files
+		 */
+		_onClickDeleteSelected: function(event) {
+			var files = null;
+			if (!this.isAllSelected()) {
+				files = _.pluck(this.getSelectedFiles(), 'name');
+			}
+			this.do_delete(files);
+			event.preventDefault();
+			return false;
+		},
+
+		/**
+		 * Event handler when clicking on a table header
+		 */
+		_onClickHeader: function(e) {
+			var $target = $(e.target);
+			var sort;
+			if (!$target.is('a')) {
+				$target = $target.closest('a');
+			}
+			sort = $target.attr('data-sort');
+			if (sort) {
+				if (this._sort === sort) {
+					this.setSort(sort, (this._sortDirection === 'desc')?'asc':'desc');
 				}
+				else {
+					this.setSort(sort, 'asc');
+				}
+				this.reload();
 			}
-		}
-	},
+		},
+
+		/**
+		 * Event handler when clicking on a bread crumb
+		 */
+		_onClickBreadCrumb: function(e) {
+			var $el = $(e.target).closest('.crumb'),
+				$targetDir = $el.data('dir');
+
+			if ($targetDir !== undefined) {
+				e.preventDefault();
+				this.changeDirectory($targetDir);
+			}
+		},
 
-	/**
-	 * Event handler for when clicking on a file's checkbox
-	 */
-	_onClickFileCheckbox: function(e) {
-		var $tr = $(e.target).closest('tr');
-		this._selectFileEl($tr, !$tr.hasClass('selected'));
-		this._lastChecked = $tr;
-		this.updateSelectionSummary();
-	},
+		_onScroll: function(e) {
+			if ($(window).scrollTop() + $(window).height() > $(document).height() - 500) {
+				this._nextPage(true);
+			}
+		},
+
+		/**
+		 * Event handler when dropping on a breadcrumb
+		 */
+		_onDropOnBreadCrumb: function( event, ui ) {
+			var $target = $(event.target);
+			if (!$target.is('.crumb')) {
+				$target = $target.closest('.crumb');
+			}
+			var targetPath = $(event.target).data('dir');
+			var dir = this.getCurrentDirectory();
+			while (dir.substr(0,1) === '/') {//remove extra leading /'s
+				dir = dir.substr(1);
+			}
+			dir = '/' + dir;
+			if (dir.substr(-1,1) !== '/') {
+				dir = dir + '/';
+			}
+			// do nothing if dragged on current dir
+			if (targetPath === dir || targetPath + '/' === dir) {
+				return;
+			}
 
-	/**
-	 * Event handler for when selecting/deselecting all files
-	 */
-	_onClickSelectAll: function(e) {
-		var checked = $(e.target).prop('checked');
-		this.$fileList.find('td.filename>input:checkbox').prop('checked', checked)
-			.closest('tr').toggleClass('selected', checked);
-		this._selectedFiles = {};
-		this._selectionSummary.clear();
-		if (checked) {
-			for (var i = 0; i < this.files.length; i++) {
-				var fileData = this.files[i];
-				this._selectedFiles[fileData.id] = fileData;
-				this._selectionSummary.add(fileData);
+			var files = this.getSelectedFiles();
+			if (files.length === 0) {
+				// single one selected without checkbox?
+				files = _.map(ui.helper.find('tr'), this.elementToFile);
 			}
-		}
-		this.updateSelectionSummary();
-	},
 
-	/**
-	 * Event handler for when clicking on "Download" for the selected files
-	 */
-	_onClickDownloadSelected: function(event) {
-		var files;
-		var dir = this.getCurrentDirectory();
-		if (this.isAllSelected()) {
-			files = OC.basename(dir);
-			dir = OC.dirname(dir) || '/';
-		}
-		else {
-			files = _.pluck(this.getSelectedFiles(), 'name');
-		}
-		OC.Notification.show(t('files','Your download is being prepared. This might take some time if the files are big.'));
-		OC.redirect(Files.getDownloadUrl(files, dir));
-		return false;
-	},
+			this.move(_.pluck(files, 'name'), targetPath);
+		},
 
-	/**
-	 * Event handler for when clicking on "Delete" for the selected files
-	 */
-	_onClickDeleteSelected: function(event) {
-		var files = null;
-		if (!FileList.isAllSelected()) {
-			files = _.pluck(this.getSelectedFiles(), 'name');
-		}
-		this.do_delete(files);
-		event.preventDefault();
-		return false;
-	},
+		/**
+		 * Sets a new page title
+		 */
+		setPageTitle: function(title){
+			if (title) {
+				title += ' - ';
+			} else {
+				title = '';
+			}
+			title += this.appName;
+			// Sets the page title with the " - ownCloud" suffix as in templates
+			window.document.title = title + ' - ' + oc_defaults.title;
 
-	/**
-	 * Event handler when clicking on a table header
-	 */
-	_onClickHeader: function(e) {
-		var $target = $(e.target);
-		var sort;
-		if (!$target.is('a')) {
-			$target = $target.closest('a');
-		}
-		sort = $target.attr('data-sort');
-		if (sort) {
-			if (this._sort === sort) {
-				this.setSort(sort, (this._sortDirection === 'desc')?'asc':'desc');
+			return true;
+		},
+		/**
+		 * Returns the tr element for a given file name
+		 * @param fileName file name
+		 */
+		findFileEl: function(fileName){
+			// use filterAttr to avoid escaping issues
+			return this.$fileList.find('tr').filterAttr('data-file', fileName);
+		},
+
+		/**
+		 * Returns the file data from a given file element.
+		 * @param $el file tr element
+		 * @return file data
+		 */
+		elementToFile: function($el){
+			$el = $($el);
+			return {
+				id: parseInt($el.attr('data-id'), 10),
+				name: $el.attr('data-file'),
+				mimetype: $el.attr('data-mime'),
+				type: $el.attr('data-type'),
+				size: parseInt($el.attr('data-size'), 10),
+				etag: $el.attr('data-etag')
+			};
+		},
+
+		/**
+		 * Appends the next page of files into the table
+		 * @param animate true to animate the new elements
+		 */
+		_nextPage: function(animate) {
+			var index = this.$fileList.children().length,
+				count = this.pageSize,
+				tr,
+				fileData,
+				newTrs = [],
+				isAllSelected = this.isAllSelected();
+
+			if (index >= this.files.length) {
+				return;
 			}
-			else {
-				this.setSort(sort, 'asc');
+
+			while (count > 0 && index < this.files.length) {
+				fileData = this.files[index];
+				tr = this._renderRow(fileData, {updateSummary: false});
+				this.$fileList.append(tr);
+				if (isAllSelected || this._selectedFiles[fileData.id]) {
+					tr.addClass('selected');
+					tr.find('input:checkbox').prop('checked', true);
+				}
+				if (animate) {
+					tr.addClass('appear transparent');
+					newTrs.push(tr);
+				}
+				index++;
+				count--;
 			}
-			this.reload();
-		}
-	},
 
-	/**
-	 * Event handler when clicking on a bread crumb
-	 */
-	_onClickBreadCrumb: function(e) {
-		var $el = $(e.target).closest('.crumb'),
-			$targetDir = $el.data('dir');
+			if (animate) {
+				// defer, for animation
+				window.setTimeout(function() {
+					for (var i = 0; i < newTrs.length; i++ ) {
+						newTrs[i].removeClass('transparent');
+					}
+				}, 0);
+			}
+		},
 
-		if ($targetDir !== undefined) {
-			e.preventDefault();
-			FileList.changeDirectory($targetDir);
-		}
-	},
+		/**
+		 * Sets the files to be displayed in the list.
+		 * This operation will re-render the list and update the summary.
+		 * @param filesArray array of file data (map)
+		 */
+		setFiles: function(filesArray) {
+			// detach to make adding multiple rows faster
+			this.files = filesArray;
 
-	_onScroll: function(e) {
-		if ($(window).scrollTop() + $(window).height() > $(document).height() - 500) {
-			this._nextPage(true);
-		}
-	},
+			this.$fileList.detach();
+			this.$fileList.empty();
 
-	/**
-	 * Event handler when dropping on a breadcrumb
-	 */
-	_onDropOnBreadCrumb: function( event, ui ) {
-		var $target = $(event.target);
-		if (!$target.is('.crumb')) {
-			$target = $target.closest('.crumb');
-		}
-		var targetPath = $(event.target).data('dir');
-		var dir = this.getCurrentDirectory();
-		while (dir.substr(0,1) === '/') {//remove extra leading /'s
-			dir = dir.substr(1);
-		}
-		dir = '/' + dir;
-		if (dir.substr(-1,1) !== '/') {
-			dir = dir + '/';
-		}
-		// do nothing if dragged on current dir
-		if (targetPath === dir || targetPath + '/' === dir) {
-			return;
-		}
+			// clear "Select all" checkbox
+			this.$el.find('#select_all').prop('checked', false);
 
-		var files = this.getSelectedFiles();
-		if (files.length === 0) {
-			// single one selected without checkbox?
-			files = _.map(ui.helper.find('tr'), FileList.elementToFile);
-		}
+			this.isEmpty = this.files.length === 0;
+			this._nextPage();
 
-		FileList.move(_.pluck(files, 'name'), targetPath);
-	},
+			this.$el.find('thead').after(this.$fileList);
 
-	/**
-	 * Sets a new page title
-	 */
-	setPageTitle: function(title){
-		if (title) {
-			title += ' - ';
-		} else {
-			title = '';
-		}
-		title += FileList.appName;
-		// Sets the page title with the " - ownCloud" suffix as in templates
-		window.document.title = title + ' - ' + oc_defaults.title;
+			this.updateEmptyContent();
+			this.$fileList.trigger(jQuery.Event("fileActionsReady"));
+			// "Files" might not be loaded in extending apps
+			if (window.Files) {
+				Files.setupDragAndDrop();
+			}
 
-		return true;
-	},
-	/**
-	 * Returns the tr element for a given file name
-	 * @param fileName file name
-	 */
-	findFileEl: function(fileName){
-		// use filterAttr to avoid escaping issues
-		return this.$fileList.find('tr').filterAttr('data-file', fileName);
-	},
+			this.fileSummary.calculate(filesArray);
 
-	/**
-	 * Returns the file data from a given file element.
-	 * @param $el file tr element
-	 * @return file data
-	 */
-	elementToFile: function($el){
-		$el = $($el);
-		return {
-			id: parseInt($el.attr('data-id'), 10),
-			name: $el.attr('data-file'),
-			mimetype: $el.attr('data-mime'),
-			type: $el.attr('data-type'),
-			size: parseInt($el.attr('data-size'), 10),
-			etag: $el.attr('data-etag')
-		};
-	},
+			this.updateSelectionSummary();
+			$(window).scrollTop(0);
+
+			this.$fileList.trigger(jQuery.Event("updated"));
+		},
+		/**
+		 * Creates a new table row element using the given file data.
+		 * @param fileData map of file attributes
+		 * @param options map of attribute "loading" whether the entry is currently loading
+		 * @return new tr element (not appended to the table)
+		 */
+		_createRow: function(fileData, options) {
+			var td, simpleSize, basename, extension, sizeColor,
+				icon = OC.Util.replaceSVGIcon(fileData.icon),
+				name = fileData.name,
+				type = fileData.type || 'file',
+				mtime = parseInt(fileData.mtime, 10) || new Date().getTime(),
+				mime = fileData.mimetype,
+				linkUrl;
+			options = options || {};
+
+			if (type === 'dir') {
+				mime = mime || 'httpd/unix-directory';
+			}
 
-	/**
-	 * Appends the next page of files into the table
-	 * @param animate true to animate the new elements
-	 */
-	_nextPage: function(animate) {
-		var index = this.$fileList.children().length,
-			count = this.pageSize,
-			tr,
-			fileData,
-			newTrs = [],
-			isAllSelected = this.isAllSelected();
-
-		if (index >= this.files.length) {
-			return;
-		}
+			// user should always be able to rename a share mount point
+			var allowRename = 0;
+			if (fileData.isShareMountPoint) {
+				allowRename = OC.PERMISSION_UPDATE;
+			}
+
+			//containing tr
+			var tr = $('<tr></tr>').attr({
+				"data-id" : fileData.id,
+				"data-type": type,
+				"data-size": fileData.size,
+				"data-file": name,
+				"data-mime": mime,
+				"data-mtime": mtime,
+				"data-etag": fileData.etag,
+				"data-permissions": fileData.permissions | allowRename || this.getDirectoryPermissions()
+			});
 
-		while (count > 0 && index < this.files.length) {
-			fileData = this.files[index];
-			tr = this._renderRow(fileData, {updateSummary: false});
-			this.$fileList.append(tr);
-			if (isAllSelected || this._selectedFiles[fileData.id]) {
-				tr.addClass('selected');
-				tr.find('input:checkbox').prop('checked', true);
+			if (type === 'dir') {
+				// use default folder icon
+				icon = icon || OC.imagePath('core', 'filetypes/folder');
 			}
-			if (animate) {
-				tr.addClass('appear transparent');
-				newTrs.push(tr);
+			else {
+				icon = icon || OC.imagePath('core', 'filetypes/file');
 			}
-			index++;
-			count--;
-		}
 
-		if (animate) {
-			// defer, for animation
-			window.setTimeout(function() {
-				for (var i = 0; i < newTrs.length; i++ ) {
-					newTrs[i].removeClass('transparent');
-				}
-			}, 0);
-		}
-	},
+			// filename td
+			td = $('<td></td>').attr({
+				"class": "filename",
+				"style": 'background-image:url(' + icon + '); background-size: 32px;'
+			});
 
-	/**
-	 * Sets the files to be displayed in the list.
-	 * This operation will re-render the list and update the summary.
-	 * @param filesArray array of file data (map)
-	 */
-	setFiles: function(filesArray) {
-		// detach to make adding multiple rows faster
-		this.files = filesArray;
+			// linkUrl
+			if (type === 'dir') {
+				linkUrl = this.linkTo(this.getCurrentDirectory() + '/' + name);
+			}
+			else {
+				linkUrl = this.getDownloadUrl(name, this.getCurrentDirectory());
+			}
+			td.append('<input id="select-' + fileData.id + '" type="checkbox" /><label for="select-' + fileData.id + '"></label>');
+			var linkElem = $('<a></a>').attr({
+				"class": "name",
+				"href": linkUrl
+			});
 
-		this.$fileList.detach();
-		this.$fileList.empty();
+			// from here work on the display name
+			name = fileData.displayName || name;
 
-		// clear "Select all" checkbox
-		this.$el.find('#select_all').prop('checked', false);
+			// split extension from filename for non dirs
+			if (type !== 'dir' && name.indexOf('.') !== -1) {
+				basename = name.substr(0, name.lastIndexOf('.'));
+				extension = name.substr(name.lastIndexOf('.'));
+			} else {
+				basename = name;
+				extension = false;
+			}
+			var nameSpan=$('<span></span>').addClass('nametext').text(basename);
+			linkElem.append(nameSpan);
+			if (extension) {
+				nameSpan.append($('<span></span>').addClass('extension').text(extension));
+			}
+			// dirs can show the number of uploaded files
+			if (type === 'dir') {
+				linkElem.append($('<span></span>').attr({
+					'class': 'uploadtext',
+					'currentUploads': 0
+				}));
+			}
+			td.append(linkElem);
+			tr.append(td);
 
-		this.isEmpty = this.files.length === 0;
-		this._nextPage();
+			// size column
+			if (typeof(fileData.size) !== 'undefined' && fileData.size >= 0) {
+				simpleSize = humanFileSize(parseInt(fileData.size, 10));
+				sizeColor = Math.round(160-Math.pow((fileData.size/(1024*1024)),2));
+			} else {
+				simpleSize = t('files', 'Pending');
+			}
 
-		this.$el.find('thead').after(this.$fileList);
+			td = $('<td></td>').attr({
+				"class": "filesize",
+				"style": 'color:rgb(' + sizeColor + ',' + sizeColor + ',' + sizeColor + ')'
+			}).text(simpleSize);
+			tr.append(td);
+
+			// date column
+			var modifiedColor = Math.round((Math.round((new Date()).getTime() / 1000) - mtime)/60/60/24*5);
+			td = $('<td></td>').attr({ "class": "date" });
+			td.append($('<span></span>').attr({
+				"class": "modified",
+				"title": formatDate(mtime),
+				"style": 'color:rgb('+modifiedColor+','+modifiedColor+','+modifiedColor+')'
+			}).text( relative_modified_date(mtime / 1000) ));
+			tr.find('.filesize').text(simpleSize);
+			tr.append(td);
+			return tr;
+		},
+
+		/**
+		 * Adds an entry to the files array and also into the DOM
+		 * in a sorted manner.
+		 *
+		 * @param fileData map of file attributes
+		 * @param options map of attributes:
+		 * - "updateSummary" true to update the summary after adding (default), false otherwise
+		 * @return new tr element (not appended to the table)
+		 */
+		add: function(fileData, options) {
+			var index = -1;
+			var $tr;
+			var $rows;
+			var $insertionPoint;
+			options = options || {};
+
+			// there are three situations to cover:
+			// 1) insertion point is visible on the current page
+			// 2) insertion point is on a not visible page (visible after scrolling)
+			// 3) insertion point is at the end of the list
+
+			$rows = this.$fileList.children();
+			index = this._findInsertionIndex(fileData);
+			if (index > this.files.length) {
+				index = this.files.length;
+			}
+			else {
+				$insertionPoint = $rows.eq(index);
+			}
 
-		this.updateEmptyContent();
-		this.$fileList.trigger(jQuery.Event("fileActionsReady"));
-		// "Files" might not be loaded in extending apps
-		if (window.Files) {
-			Files.setupDragAndDrop();
-		}
+			// is the insertion point visible ?
+			if ($insertionPoint.length) {
+				// only render if it will really be inserted
+				$tr = this._renderRow(fileData, options);
+				$insertionPoint.before($tr);
+			}
+			else {
+				// if insertion point is after the last visible
+				// entry, append
+				if (index === $rows.length) {
+					$tr = this._renderRow(fileData, options);
+					this.$fileList.append($tr);
+				}
+			}
 
-		this.fileSummary.calculate(filesArray);
+			this.isEmpty = false;
+			this.files.splice(index, 0, fileData);
 
-		FileList.updateSelectionSummary();
-		$(window).scrollTop(0);
+			if ($tr && options.animate) {
+				$tr.addClass('appear transparent');
+				window.setTimeout(function() {
+					$tr.removeClass('transparent');
+				});
+			}
 
-		this.$fileList.trigger(jQuery.Event("updated"));
-	},
-	/**
-	 * Creates a new table row element using the given file data.
-	 * @param fileData map of file attributes
-	 * @param options map of attribute "loading" whether the entry is currently loading
-	 * @return new tr element (not appended to the table)
-	 */
-	_createRow: function(fileData, options) {
-		var td, simpleSize, basename, extension, sizeColor,
-			icon = OC.Util.replaceSVGIcon(fileData.icon),
-			name = fileData.name,
-			type = fileData.type || 'file',
-			mtime = parseInt(fileData.mtime, 10) || new Date().getTime(),
-			mime = fileData.mimetype,
-			linkUrl;
-		options = options || {};
-
-		if (type === 'dir') {
-			mime = mime || 'httpd/unix-directory';
-		}
+			// defaults to true if not defined
+			if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
+				this.fileSummary.add(fileData, true);
+				this.updateEmptyContent();
+			}
 
-		// user should always be able to rename a share mount point
-		var allowRename = 0;
-		if (fileData.isShareMountPoint) {
-			allowRename = OC.PERMISSION_UPDATE;
-		}
+			return $tr;
+		},
+
+		/**
+		 * Creates a new row element based on the given attributes
+		 * and returns it.
+		 *
+		 * @param fileData map of file attributes
+		 * @param options map of attributes:
+		 * - "index" optional index at which to insert the element
+		 * - "updateSummary" true to update the summary after adding (default), false otherwise
+		 * @return new tr element (not appended to the table)
+		 */
+		_renderRow: function(fileData, options) {
+			options = options || {};
+			var type = fileData.type || 'file',
+				mime = fileData.mimetype,
+				permissions = parseInt(fileData.permissions, 10) || 0;
+
+			if (fileData.isShareMountPoint) {
+				permissions = permissions | OC.PERMISSION_UPDATE;
+			}
 
-		//containing tr
-		var tr = $('<tr></tr>').attr({
-			"data-id" : fileData.id,
-			"data-type": type,
-			"data-size": fileData.size,
-			"data-file": name,
-			"data-mime": mime,
-			"data-mtime": mtime,
-			"data-etag": fileData.etag,
-			"data-permissions": fileData.permissions | allowRename || this.getDirectoryPermissions()
-		});
-
-		if (type === 'dir') {
-			// use default folder icon
-			icon = icon || OC.imagePath('core', 'filetypes/folder');
-		}
-		else {
-			icon = icon || OC.imagePath('core', 'filetypes/file');
-		}
+			if (type === 'dir') {
+				mime = mime || 'httpd/unix-directory';
+			}
+			var tr = this._createRow(
+				fileData,
+				options
+			);
+			var filenameTd = tr.find('td.filename');
+
+			// TODO: move dragging to FileActions ?
+			// enable drag only for deletable files
+			if (permissions & OC.PERMISSION_DELETE) {
+				filenameTd.draggable(dragOptions);
+			}
+			// allow dropping on folders
+			if (fileData.type === 'dir') {
+				filenameTd.droppable(folderDropOptions);
+			}
 
-		// filename td
-		td = $('<td></td>').attr({
-			"class": "filename",
-			"style": 'background-image:url(' + icon + '); background-size: 32px;'
-		});
+			if (options.hidden) {
+				tr.addClass('hidden');
+			}
 
-		// linkUrl
-		if (type === 'dir') {
-			linkUrl = FileList.linkTo(FileList.getCurrentDirectory() + '/' + name);
-		}
-		else {
-			linkUrl = Files.getDownloadUrl(name, FileList.getCurrentDirectory());
-		}
-		td.append('<input id="select-' + fileData.id + '" type="checkbox" /><label for="select-' + fileData.id + '"></label>');
-		var linkElem = $('<a></a>').attr({
-			"class": "name",
-			"href": linkUrl
-		});
-
-		// from here work on the display name
-		name = fileData.displayName || name;
-
-		// split extension from filename for non dirs
-		if (type !== 'dir' && name.indexOf('.') !== -1) {
-			basename = name.substr(0, name.lastIndexOf('.'));
-			extension = name.substr(name.lastIndexOf('.'));
-		} else {
-			basename = name;
-			extension = false;
-		}
-		var nameSpan=$('<span></span>').addClass('nametext').text(basename);
-		linkElem.append(nameSpan);
-		if (extension) {
-			nameSpan.append($('<span></span>').addClass('extension').text(extension));
-		}
-		// dirs can show the number of uploaded files
-		if (type === 'dir') {
-			linkElem.append($('<span></span>').attr({
-				'class': 'uploadtext',
-				'currentUploads': 0
-			}));
-		}
-		td.append(linkElem);
-		tr.append(td);
-
-		// size column
-		if (typeof(fileData.size) !== 'undefined' && fileData.size >= 0) {
-			simpleSize = humanFileSize(parseInt(fileData.size, 10));
-			sizeColor = Math.round(160-Math.pow((fileData.size/(1024*1024)),2));
-		} else {
-			simpleSize = t('files', 'Pending');
-		}
+			// display actions
+			this.fileActions.display(filenameTd, false);
+
+			if (fileData.isPreviewAvailable) {
+				// lazy load / newly inserted td ?
+				if (!fileData.icon) {
+					this.lazyLoadPreview({
+						path: this.getCurrentDirectory() + '/' + fileData.name,
+						mime: mime,
+						etag: fileData.etag,
+						callback: function(url) {
+							filenameTd.css('background-image', 'url(' + url + ')');
+						}
+					});
+				}
+				else {
+					// set the preview URL directly
+					var urlSpec = {
+							file: this.getCurrentDirectory() + '/' + fileData.name,
+							c: fileData.etag
+						};
+					var previewUrl = this.generatePreviewUrl(urlSpec);
+					previewUrl = previewUrl.replace('(', '%28').replace(')', '%29');
+					filenameTd.css('background-image', 'url(' + previewUrl + ')');
+				}
+			}
+			return tr;
+		},
+		/**
+		 * Returns the current directory
+		 * @return current directory
+		 */
+		getCurrentDirectory: function(){
+			return this._currentDirectory || this.$el.find('#dir').val() || '/';
+		},
+		/**
+		 * Returns the directory permissions
+		 * @return permission value as integer
+		 */
+		getDirectoryPermissions: function() {
+			return parseInt(this.$el.find('#permissions').val(), 10);
+		},
+		/**
+		 * @brief Changes the current directory and reload the file list.
+		 * @param targetDir target directory (non URL encoded)
+		 * @param changeUrl false if the URL must not be changed (defaults to true)
+		 * @param {boolean} force set to true to force changing directory
+		 */
+		changeDirectory: function(targetDir, changeUrl, force) {
+			var currentDir = this.getCurrentDirectory();
+			targetDir = targetDir || '/';
+			if (!force && currentDir === targetDir) {
+				return;
+			}
+			this._setCurrentDir(targetDir, changeUrl);
+			this.reload();
+		},
+		linkTo: function(dir) {
+			return OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/');
+		},
+
+		/**
+		 * Sets the file actions handler
+		 */
+		setFileActions: function(fileActions) {
+			this.fileActions = fileActions;
+		},
+
+		/**
+		 * Sets the current directory name and updates the breadcrumb.
+		 * @param targetDir directory to display
+		 * @param changeUrl true to also update the URL, false otherwise (default)
+		 */
+		_setCurrentDir: function(targetDir, changeUrl) {
+			var url,
+				previousDir = this.getCurrentDirectory(),
+				baseDir = OC.basename(targetDir);
+
+			if (baseDir !== '') {
+				this.setPageTitle(baseDir);
+			}
+			else {
+				this.setPageTitle();
+			}
 
-		td = $('<td></td>').attr({
-			"class": "filesize",
-			"style": 'color:rgb(' + sizeColor + ',' + sizeColor + ',' + sizeColor + ')'
-		}).text(simpleSize);
-		tr.append(td);
-
-		// date column
-		var modifiedColor = Math.round((Math.round((new Date()).getTime() / 1000) - mtime)/60/60/24*5);
-		td = $('<td></td>').attr({ "class": "date" });
-		td.append($('<span></span>').attr({
-			"class": "modified",
-			"title": formatDate(mtime),
-			"style": 'color:rgb('+modifiedColor+','+modifiedColor+','+modifiedColor+')'
-		}).text( relative_modified_date(mtime / 1000) ));
-		tr.find('.filesize').text(simpleSize);
-		tr.append(td);
-		return tr;
-	},
+			this._currentDirectory = targetDir;
 
-	/**
-	 * Adds an entry to the files array and also into the DOM
-	 * in a sorted manner.
-	 *
-	 * @param fileData map of file attributes
-	 * @param options map of attributes:
-	 * - "updateSummary" true to update the summary after adding (default), false otherwise
-	 * @return new tr element (not appended to the table)
-	 */
-	add: function(fileData, options) {
-		var index = -1;
-		var $tr;
-		var $rows;
-		var $insertionPoint;
-		options = options || {};
-
-		// there are three situations to cover:
-		// 1) insertion point is visible on the current page
-		// 2) insertion point is on a not visible page (visible after scrolling)
-		// 3) insertion point is at the end of the list
-
-		$rows = this.$fileList.children();
-		index = this._findInsertionIndex(fileData);
-		if (index > this.files.length) {
-			index = this.files.length;
-		}
-		else {
-			$insertionPoint = $rows.eq(index);
-		}
+			// legacy stuff
+			this.$el.find('#dir').val(targetDir);
 
-		// is the insertion point visible ?
-		if ($insertionPoint.length) {
-			// only render if it will really be inserted
-			$tr = this._renderRow(fileData, options);
-			$insertionPoint.before($tr);
-		}
-		else {
-			// if insertion point is after the last visible
-			// entry, append
-			if (index === $rows.length) {
-				$tr = this._renderRow(fileData, options);
-				this.$fileList.append($tr);
+			if (changeUrl !== false) {
+				this.$el.trigger(jQuery.Event('changeDirectory', {
+					dir: targetDir,
+					previousDir: previousDir
+				}));
 			}
-		}
-
-		this.isEmpty = false;
-		this.files.splice(index, 0, fileData);
-
-		if ($tr && options.animate) {
-			$tr.addClass('appear transparent');
-			window.setTimeout(function() {
-				$tr.removeClass('transparent');
+			this.breadcrumb.setDirectory(this.getCurrentDirectory());
+		},
+		/**
+		 * Sets the current sorting and refreshes the list
+		 *
+		 * @param sort sort attribute name
+		 * @param direction sort direction, one of "asc" or "desc"
+		 */
+		setSort: function(sort, direction) {
+			var comparator = FileList.Comparators[sort] || FileList.Comparators.name;
+			this._sort = sort;
+			this._sortDirection = (direction === 'desc')?'desc':'asc';
+			this._sortComparator = comparator;
+			if (direction === 'desc') {
+				this._sortComparator = function(fileInfo1, fileInfo2) {
+					return -comparator(fileInfo1, fileInfo2);
+				};
+			}
+			this.$el.find('thead th .sort-indicator')
+				.removeClass(this.SORT_INDICATOR_ASC_CLASS + ' ' + this.SORT_INDICATOR_DESC_CLASS);
+			this.$el.find('thead th.column-' + sort + ' .sort-indicator')
+				.addClass(direction === 'desc' ? this.SORT_INDICATOR_DESC_CLASS : this.SORT_INDICATOR_ASC_CLASS);
+		},
+		/**
+		 * @brief Reloads the file list using ajax call
+		 */
+		reload: function() {
+			var self = this;
+			this._selectedFiles = {};
+			this._selectionSummary.clear();
+			this.$el.find('#select_all').prop('checked', false);
+			this.showMask();
+			if (this._reloadCall) {
+				this._reloadCall.abort();
+			}
+			this._reloadCall = $.ajax({
+				url: this.getAjaxUrl('list'),
+				data: {
+					dir : this.getCurrentDirectory(),
+					sort: this._sort,
+					sortdirection: this._sortDirection
+				},
+				error: function(result) {
+					self.reloadCallback(result);
+				},
+				success: function(result) {
+					self.reloadCallback(result);
+				}
 			});
-		}
-
-		// defaults to true if not defined
-		if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
-			this.fileSummary.add(fileData, true);
-			this.updateEmptyContent();
-		}
-
-		return $tr;
-	},
-
-	/**
-	 * Creates a new row element based on the given attributes
-	 * and returns it.
-	 *
-	 * @param fileData map of file attributes
-	 * @param options map of attributes:
-	 * - "index" optional index at which to insert the element
-	 * - "updateSummary" true to update the summary after adding (default), false otherwise
-	 * @return new tr element (not appended to the table)
-	 */
-	_renderRow: function(fileData, options) {
-		options = options || {};
-		var type = fileData.type || 'file',
-			mime = fileData.mimetype,
-			permissions = parseInt(fileData.permissions, 10) || 0;
-
-		if (fileData.isShareMountPoint) {
-			permissions = permissions | OC.PERMISSION_UPDATE;
-		}
+		},
+		reloadCallback: function(result) {
+			delete this._reloadCall;
+			this.hideMask();
+
+			if (!result || result.status === 'error') {
+				OC.Notification.show(result.data.message);
+				return;
+			}
 
-		if (type === 'dir') {
-			mime = mime || 'httpd/unix-directory';
-		}
-		var tr = this._createRow(
-			fileData,
-			options
-		);
-		var filenameTd = tr.find('td.filename');
-
-		// TODO: move dragging to FileActions ?
-		// enable drag only for deletable files
-		if (permissions & OC.PERMISSION_DELETE) {
-			filenameTd.draggable(dragOptions);
-		}
-		// allow dropping on folders
-		if (fileData.type === 'dir') {
-			filenameTd.droppable(folderDropOptions);
-		}
+			if (result.status === 404) {
+				// go back home
+				this.changeDirectory('/');
+				return;
+			}
+			// aborted ?
+			if (result.status === 0){
+				return;
+			}
 
-		if (options.hidden) {
-			tr.addClass('hidden');
-		}
+			// TODO: should rather return upload file size through
+			// the files list ajax call
+			Files.updateStorageStatistics(true);
 
-		// display actions
-		FileActions.display(filenameTd, false);
+			if (result.data.permissions) {
+				this.setDirectoryPermissions(result.data.permissions);
+			}
 
-		if (fileData.isPreviewAvailable) {
-			// lazy load / newly inserted td ?
-			if (!fileData.icon) {
-				Files.lazyLoadPreview(getPathForPreview(fileData.name), mime, function(url) {
-					filenameTd.css('background-image', 'url(' + url + ')');
-				}, null, null, fileData.etag);
+			this.setFiles(result.data.files);
+		},
+
+		getAjaxUrl: function(action, params) {
+			return Files.getAjaxUrl(action, params);
+		},
+
+		getDownloadUrl: function(files, dir) {
+			return Files.getDownloadUrl(files, dir || this.getCurrentDirectory());
+		},
+
+		/**
+		 * Generates a preview URL based on the URL space.
+		 * @param urlSpec map with {x: width, y: height, file: file path}
+		 * @return preview URL
+		 */
+		generatePreviewUrl: function(urlSpec) {
+			urlSpec = urlSpec || {};
+			if (!urlSpec.x) {
+				urlSpec.x = this.$table.data('preview-x') || 36;
 			}
-			else {
-				// set the preview URL directly
-				var urlSpec = {
-						file: FileList.getCurrentDirectory() + '/' + fileData.name,
-						c: fileData.etag
-					};
-				var previewUrl = Files.generatePreviewUrl(urlSpec);
-				previewUrl = previewUrl.replace('(', '%28').replace(')', '%29');
-				filenameTd.css('background-image', 'url(' + previewUrl + ')');
+			if (!urlSpec.y) {
+				urlSpec.y = this.$table.data('preview-y') || 36;
 			}
-		}
-		return tr;
-	},
-	/**
-	 * Returns the current directory
-	 * @return current directory
-	 */
-	getCurrentDirectory: function(){
-		return $('#dir').val() || '/';
-	},
-	/**
-	 * Returns the directory permissions
-	 * @return permission value as integer
-	 */
-	getDirectoryPermissions: function() {
-		return parseInt($('#permissions').val(), 10);
-	},
-	/**
-	 * @brief Changes the current directory and reload the file list.
-	 * @param targetDir target directory (non URL encoded)
-	 * @param changeUrl false if the URL must not be changed (defaults to true)
-	 * @param {boolean} force set to true to force changing directory
-	 */
-	changeDirectory: function(targetDir, changeUrl, force) {
-		var $dir = $('#dir'),
-			currentDir = $dir.val() || '/';
-		targetDir = targetDir || '/';
-		if (!force && currentDir === targetDir) {
-			return;
-		}
-		FileList._setCurrentDir(targetDir, changeUrl);
-		$('#fileList').trigger(
-			jQuery.Event('changeDirectory', {
-				dir: targetDir,
-				previousDir: currentDir
-			}
-		));
-		this.reload();
-	},
-	linkTo: function(dir) {
-		return OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/');
-	},
-
-	/**
-	 * Sets the current directory name and updates the breadcrumb.
-	 * @param targetDir directory to display
-	 * @param changeUrl true to also update the URL, false otherwise (default)
-	 */
-	_setCurrentDir: function(targetDir, changeUrl) {
-		var url,
-			baseDir = OC.basename(targetDir);
+			urlSpec.y *= window.devicePixelRatio;
+			urlSpec.x *= window.devicePixelRatio;
+			urlSpec.forceIcon = 0;
+			return OC.generateUrl('/core/preview.png?') + $.param(urlSpec);
+		},
+
+		/**
+		 * Lazy load a file's preview.
+		 * 
+		 * @param path path of the file
+		 * @param mime mime type
+		 * @param callback callback function to call when the image was loaded
+		 * @param etag file etag (for caching)
+		 */
+		lazyLoadPreview : function(options) {
+			var self = this;
+			var path = options.path;
+			var mime = options.mime;
+			var ready = options.callback;
+			var etag = options.etag;
+
+			// get mime icon url
+			OCA.Files.Files.getMimeIcon(mime, function(iconURL) {
+				var previewURL,
+					urlSpec = {};
+				ready(iconURL); // set mimeicon URL
+
+				urlSpec.file = OCA.Files.Files.fixPath(path);
+
+				if (etag){
+					// use etag as cache buster
+					urlSpec.c = etag;
+				}
+				else {
+					console.warn('OCA.Files.FileList.lazyLoadPreview(): missing etag argument');
+				}
 
-		if (baseDir !== '') {
-			FileList.setPageTitle(baseDir);
-		}
-		else {
-			FileList.setPageTitle();
-		}
+				previewURL = self.generatePreviewUrl(urlSpec);
+				previewURL = previewURL.replace('(', '%28');
+				previewURL = previewURL.replace(')', '%29');
+
+				// preload image to prevent delay
+				// this will make the browser cache the image
+				var img = new Image();
+				img.onload = function(){
+					// if loading the preview image failed (no preview for the mimetype) then img.width will < 5
+					if (img.width > 5) {
+						ready(previewURL);
+					}
+				};
+				img.src = previewURL;
+			});
+		},
 
-		$('#dir').val(targetDir);
-		if (changeUrl !== false) {
-			if (window.history.pushState && changeUrl !== false) {
-				url = FileList.linkTo(targetDir);
-				window.history.pushState({dir: targetDir}, '', url);
+		setDirectoryPermissions: function(permissions) {
+			var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
+			this.$el.find('#permissions').val(permissions);
+			this.$el.find('.creatable').toggleClass('hidden', !isCreatable);
+			this.$el.find('.notCreatable').toggleClass('hidden', isCreatable);
+		},
+		/**
+		 * Shows/hides action buttons
+		 *
+		 * @param show true for enabling, false for disabling
+		 */
+		showActions: function(show){
+			this.$el.find('.actions,#file_action_panel').toggleClass('hidden', !show);
+			if (show){
+				// make sure to display according to permissions
+				var permissions = this.getDirectoryPermissions();
+				var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
+				this.$el.find('.creatable').toggleClass('hidden', !isCreatable);
+				this.$el.find('.notCreatable').toggleClass('hidden', isCreatable);
+				// remove old style breadcrumbs (some apps might create them)
+				this.$el.find('#controls .crumb').remove();
+				// refresh breadcrumbs in case it was replaced by an app
+				this.breadcrumb.render();
 			}
-			// use URL hash for IE8
 			else{
-				window.location.hash = '?dir='+ encodeURIComponent(targetDir).replace(/%2F/g, '/');
+				this.$el.find('.creatable, .notCreatable').addClass('hidden');
+			}
+		},
+		/**
+		 * Enables/disables viewer mode.
+		 * In viewer mode, apps can embed themselves under the controls bar.
+		 * In viewer mode, the actions of the file list will be hidden.
+		 * @param show true for enabling, false for disabling
+		 */
+		setViewerMode: function(show){
+			this.showActions(!show);
+			this.$el.find('#filestable').toggleClass('hidden', show);
+		},
+		/**
+		 * Removes a file entry from the list
+		 * @param name name of the file to remove
+		 * @param options optional options as map:
+		 * "updateSummary": true to update the summary (default), false otherwise
+		 * @return deleted element
+		 */
+		remove: function(name, options){
+			options = options || {};
+			var fileEl = this.findFileEl(name);
+			var index = fileEl.index();
+			if (!fileEl.length) {
+				return null;
+			}
+			if (this._selectedFiles[fileEl.data('id')]) {
+				// remove from selection first
+				this._selectFileEl(fileEl, false);
+				this.updateSelectionSummary();
+			}
+			if (fileEl.data('permissions') & OC.PERMISSION_DELETE) {
+				// file is only draggable when delete permissions are set
+				fileEl.find('td.filename').draggable('destroy');
+			}
+			this.files.splice(index, 1);
+			fileEl.remove();
+			// TODO: improve performance on batch update
+			this.isEmpty = !this.files.length;
+			if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
+				this.updateEmptyContent();
+				this.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}, true);
 			}
-		}
-		this.breadcrumb.setDirectory(this.getCurrentDirectory());
-	},
-	/**
-	 * Sets the current sorting and refreshes the list
-	 *
-	 * @param sort sort attribute name
-	 * @param direction sort direction, one of "asc" or "desc"
-	 */
-	setSort: function(sort, direction) {
-		var comparator = this.Comparators[sort] || this.Comparators.name;
-		this._sort = sort;
-		this._sortDirection = (direction === 'desc')?'desc':'asc';
-		this._sortComparator = comparator;
-		if (direction === 'desc') {
-			this._sortComparator = function(fileInfo1, fileInfo2) {
-				return -comparator(fileInfo1, fileInfo2);
-			};
-		}
-		this.$el.find('thead th .sort-indicator')
-			.removeClass(this.SORT_INDICATOR_ASC_CLASS + ' ' + this.SORT_INDICATOR_DESC_CLASS);
-		this.$el.find('thead th.column-' + sort + ' .sort-indicator')
-			.addClass(direction === 'desc' ? this.SORT_INDICATOR_DESC_CLASS : this.SORT_INDICATOR_ASC_CLASS);
-	},
-	/**
-	 * @brief Reloads the file list using ajax call
-	 */
-	reload: function() {
-		this._selectedFiles = {};
-		this._selectionSummary.clear();
-		this.$el.find('#select_all').prop('checked', false);
-		FileList.showMask();
-		if (FileList._reloadCall) {
-			FileList._reloadCall.abort();
-		}
-		FileList._reloadCall = $.ajax({
-			url: Files.getAjaxUrl('list'),
-			data: {
-				dir: $('#dir').val(),
-				sort: FileList._sort,
-				sortdirection: FileList._sortDirection
-			},
-			error: function(result) {
-				FileList.reloadCallback(result);
-			},
-			success: function(result) {
-				FileList.reloadCallback(result);
-			}
-		});
-	},
-	reloadCallback: function(result) {
-		delete this._reloadCall;
-		this.hideMask();
-
-		if (!result || result.status === 'error') {
-			OC.Notification.show(result.data.message);
-			return;
-		}
 
-		if (result.status === 404) {
-			// go back home
-			this.changeDirectory('/');
-			return;
-		}
-		// aborted ?
-		if (result.status === 0){
-			return;
-		}
+			var lastIndex = this.$fileList.children().length;
+			// if there are less elements visible than one page
+			// but there are still pending elements in the array,
+			// then directly append the next page
+			if (lastIndex < this.files.length && lastIndex < this.pageSize) {
+				this._nextPage(true);
+			}
 
-		// TODO: should rather return upload file size through
-		// the files list ajax call
-		Files.updateStorageStatistics(true);
+			return fileEl;
+		},
+		/**
+		 * Finds the index of the row before which the given
+		 * fileData should be inserted, considering the current
+		 * sorting
+		 */
+		_findInsertionIndex: function(fileData) {
+			var index = 0;
+			while (index < this.files.length && this._sortComparator(fileData, this.files[index]) > 0) {
+				index++;
+			}
+			return index;
+		},
+		/**
+		 * Moves a file to a given target folder.
+		 *
+		 * @param fileNames array of file names to move
+		 * @param targetPath absolute target path
+		 */
+		move: function(fileNames, targetPath) {
+			var self = this;
+			var dir = this.getCurrentDirectory();
+			var target = OC.basename(targetPath);
+			if (!_.isArray(fileNames)) {
+				fileNames = [fileNames];
+			}
+			_.each(fileNames, function(fileName) {
+				var $tr = self.findFileEl(fileName);
+				var $td = $tr.children('td.filename');
+				var oldBackgroundImage = $td.css('background-image');
+				$td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
+				// TODO: improve performance by sending all file names in a single call
+				$.post(
+					OC.filePath('files', 'ajax', 'move.php'),
+					{
+						dir: dir,
+						file: fileName,
+						target: targetPath
+					},
+					function(result) {
+						if (result) {
+							if (result.status === 'success') {
+								// if still viewing the same directory
+								if (self.getCurrentDirectory() === dir) {
+									// recalculate folder size
+									var oldFile = self.findFileEl(target);
+									var newFile = self.findFileEl(fileName);
+									var oldSize = oldFile.data('size');
+									var newSize = oldSize + newFile.data('size');
+									oldFile.data('size', newSize);
+									oldFile.find('td.filesize').text(OC.Util.humanFileSize(newSize));
+
+									// TODO: also update entry in FileList.files
+
+									self.remove(fileName);
+								}
+							} else {
+								OC.Notification.hide();
+								if (result.status === 'error' && result.data.message) {
+									OC.Notification.show(result.data.message);
+								}
+								else {
+									OC.Notification.show(t('files', 'Error moving file.'));
+								}
+								// hide notification after 10 sec
+								setTimeout(function() {
+									OC.Notification.hide();
+								}, 10000);
+							}
+						} else {
+							OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
+						}
+						$td.css('background-image', oldBackgroundImage);
+				});
+			});
 
-		if (result.data.permissions) {
-			this.setDirectoryPermissions(result.data.permissions);
-		}
+		},
+
+		/**
+		 * Triggers file rename input field for the given file name.
+		 * If the user enters a new name, the file will be renamed.
+		 *
+		 * @param oldname file name of the file to rename
+		 */
+		rename: function(oldname) {
+			var self = this;
+			var tr, td, input, form;
+			tr = this.findFileEl(oldname);
+			var oldFileInfo = this.files[tr.index()];
+			tr.data('renaming',true);
+			td = tr.children('td.filename');
+			input = $('<input type="text" class="filename"/>').val(oldname);
+			form = $('<form></form>');
+			form.append(input);
+			td.children('a.name').hide();
+			td.append(form);
+			input.focus();
+			//preselect input
+			var len = input.val().lastIndexOf('.');
+			if ( len === -1 ||
+				tr.data('type') === 'dir' ) {
+				len = input.val().length;
+			}
+			input.selectRange(0, len);
+			var checkInput = function () {
+				var filename = input.val();
+				if (filename !== oldname) {
+					// Files.isFileNameValid(filename) throws an exception itself
+					Files.isFileNameValid(filename);
+					if (self.inList(filename)) {
+						throw t('files', '{new_name} already exists', {new_name: filename});
+					}
+				}
+				return true;
+			};
 
-		this.setFiles(result.data.files);
-	},
-	setDirectoryPermissions: function(permissions) {
-		var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
-		$('#permissions').val(permissions);
-		$('.creatable').toggleClass('hidden', !isCreatable);
-		$('.notCreatable').toggleClass('hidden', isCreatable);
-	},
-	/**
-	 * Shows/hides action buttons
-	 *
-	 * @param show true for enabling, false for disabling
-	 */
-	showActions: function(show){
-		$('.actions,#file_action_panel').toggleClass('hidden', !show);
-		if (show){
-			// make sure to display according to permissions
-			var permissions = this.getDirectoryPermissions();
-			var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
-			$('.creatable').toggleClass('hidden', !isCreatable);
-			$('.notCreatable').toggleClass('hidden', isCreatable);
-			// remove old style breadcrumbs (some apps might create them)
-			$('#controls .crumb').remove();
-			// refresh breadcrumbs in case it was replaced by an app
-			this.breadcrumb.render();
-		}
-		else{
-			$('.creatable, .notCreatable').addClass('hidden');
-		}
-	},
-	/**
-	 * Enables/disables viewer mode.
-	 * In viewer mode, apps can embed themselves under the controls bar.
-	 * In viewer mode, the actions of the file list will be hidden.
-	 * @param show true for enabling, false for disabling
-	 */
-	setViewerMode: function(show){
-		this.showActions(!show);
-		$('#filestable').toggleClass('hidden', show);
-	},
-	/**
-	 * Removes a file entry from the list
-	 * @param name name of the file to remove
-	 * @param options optional options as map:
-	 * "updateSummary": true to update the summary (default), false otherwise
-	 * @return deleted element
-	 */
-	remove: function(name, options){
-		options = options || {};
-		var fileEl = FileList.findFileEl(name);
-		var index = fileEl.index();
-		if (!fileEl.length) {
-			return null;
-		}
-		if (this._selectedFiles[fileEl.data('id')]) {
-			// remove from selection first
-			this._selectFileEl(fileEl, false);
-			this.updateSelectionSummary();
-		}
-		if (fileEl.data('permissions') & OC.PERMISSION_DELETE) {
-			// file is only draggable when delete permissions are set
-			fileEl.find('td.filename').draggable('destroy');
-		}
-		this.files.splice(index, 1);
-		fileEl.remove();
-		// TODO: improve performance on batch update
-		FileList.isEmpty = !this.files.length;
-		if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
-			FileList.updateEmptyContent();
-			this.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}, true);
-		}
+			form.submit(function(event) {
+				event.stopPropagation();
+				event.preventDefault();
+				try {
+					var newName = input.val();
+					if (newName !== oldname) {
+						checkInput();
+						// mark as loading
+						td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
+						$.ajax({
+							url: OC.filePath('files','ajax','rename.php'),
+							data: {
+								dir : self.getCurrentDirectory(),
+								newname: newName,
+								file: oldname
+							},
+							success: function(result) {
+								var fileInfo;
+								if (!result || result.status === 'error') {
+									OC.dialogs.alert(result.data.message, t('core', 'Could not rename file'));
+									fileInfo = oldFileInfo;
+								}
+								else {
+									fileInfo = result.data;
+								}
+								// reinsert row
+								self.files.splice(tr.index(), 1);
+								tr.remove();
+								self.add(fileInfo);
+							}
+						});
+					}
+					input.tipsy('hide');
+					tr.data('renaming',false);
+					tr.attr('data-file', newName);
+					var path = td.children('a.name').attr('href');
+					// FIXME this will fail if the path contains the filename.
+					td.children('a.name').attr('href', path.replace(encodeURIComponent(oldname), encodeURIComponent(newName)));
+					var basename = newName;
+					if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') {
+						basename = newName.substr(0, newName.lastIndexOf('.'));
+					}
+					td.find('a.name span.nametext').text(basename);
+					if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') {
+						if ( ! td.find('a.name span.extension').exists() ) {
+							td.find('a.name span.nametext').append('<span class="extension"></span>');
+						}
+						td.find('a.name span.extension').text(newName.substr(newName.lastIndexOf('.')));
+					}
+					form.remove();
+					self.fileActions.display( tr.find('td.filename'), true);
+					td.children('a.name').show();
+				} catch (error) {
+					input.attr('title', error);
+					input.tipsy({gravity: 'w', trigger: 'manual'});
+					input.tipsy('show');
+					input.addClass('error');
+				}
+				return false;
+			});
+			input.keyup(function(event) {
+				// verify filename on typing
+				try {
+					checkInput();
+					input.tipsy('hide');
+					input.removeClass('error');
+				} catch (error) {
+					input.attr('title', error);
+					input.tipsy({gravity: 'w', trigger: 'manual'});
+					input.tipsy('show');
+					input.addClass('error');
+				}
+				if (event.keyCode === 27) {
+					input.tipsy('hide');
+					tr.data('renaming',false);
+					form.remove();
+					td.children('a.name').show();
+				}
+			});
+			input.click(function(event) {
+				event.stopPropagation();
+				event.preventDefault();
+			});
+			input.blur(function() {
+				form.trigger('submit');
+			});
+		},
+		inList:function(file) {
+			return this.findFileEl(file).length;
+		},
+		/**
+		 * Delete the given files from the given dir
+		 * @param files file names list (without path)
+		 * @param dir directory in which to delete the files, defaults to the current
+		 * directory
+		 */
+		do_delete:function(files, dir) {
+			var self = this;
+			var params;
+			if (files && files.substr) {
+				files=[files];
+			}
+			if (files) {
+				for (var i=0; i<files.length; i++) {
+					var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete");
+					deleteAction.removeClass('delete-icon').addClass('progress-icon');
+				}
+			}
+			// Finish any existing actions
+			if (this.lastAction) {
+				this.lastAction();
+			}
 
-		var lastIndex = this.$fileList.children().length;
-		// if there are less elements visible than one page
-		// but there are still pending elements in the array,
-		// then directly append the next page
-		if (lastIndex < this.files.length && lastIndex < this.pageSize) {
-			this._nextPage(true);
-		}
+			params = {
+				dir: dir || this.getCurrentDirectory()
+			};
+			if (files) {
+				params.files = JSON.stringify(files);
+			}
+			else {
+				// no files passed, delete all in current dir
+				params.allfiles = true;
+				// show spinner for all files
+				this.$fileList.find('tr>td.date .action.delete').removeClass('delete-icon').addClass('progress-icon');
+			}
 
-		return fileEl;
-	},
-	/**
-	 * Finds the index of the row before which the given
-	 * fileData should be inserted, considering the current
-	 * sorting
-	 */
-	_findInsertionIndex: function(fileData) {
-		var index = 0;
-		while (index < this.files.length && this._sortComparator(fileData, this.files[index]) > 0) {
-			index++;
-		}
-		return index;
-	},
-	/**
-	 * Moves a file to a given target folder.
-	 *
-	 * @param fileNames array of file names to move
-	 * @param targetPath absolute target path
-	 */
-	move: function(fileNames, targetPath) {
-		var self = this;
-		var dir = this.getCurrentDirectory();
-		var target = OC.basename(targetPath);
-		if (!_.isArray(fileNames)) {
-			fileNames = [fileNames];
-		}
-		_.each(fileNames, function(fileName) {
-			var $tr = self.findFileEl(fileName);
-			var $td = $tr.children('td.filename');
-			var oldBackgroundImage = $td.css('background-image');
-			$td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
-			// TODO: improve performance by sending all file names in a single call
-			$.post(
-				OC.filePath('files', 'ajax', 'move.php'),
-				{
-					dir: dir,
-					file: fileName,
-					target: targetPath
-				},
-				function(result) {
-					if (result) {
+			$.post(OC.filePath('files', 'ajax', 'delete.php'),
+					params,
+					function(result) {
 						if (result.status === 'success') {
-							// if still viewing the same directory
-							if (self.getCurrentDirectory() === dir) {
-								// recalculate folder size
-								var oldFile = self.findFileEl(target);
-								var newFile = self.findFileEl(fileName);
-								var oldSize = oldFile.data('size');
-								var newSize = oldSize + newFile.data('size');
-								oldFile.data('size', newSize);
-								oldFile.find('td.filesize').text(OC.Util.humanFileSize(newSize));
-
-								// TODO: also update entry in FileList.files
-
-								self.remove(fileName);
+							if (params.allfiles) {
+								self.setFiles([]);
+							}
+							else {
+								$.each(files,function(index,file) {
+									var fileEl = self.remove(file, {updateSummary: false});
+									// FIXME: not sure why we need this after the
+									// element isn't even in the DOM any more
+									fileEl.find('input[type="checkbox"]').prop('checked', false);
+									fileEl.removeClass('selected');
+									self.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')});
+								});
 							}
+							// TODO: this info should be returned by the ajax call!
+							self.updateEmptyContent();
+							self.fileSummary.update();
+							self.updateSelectionSummary();
+							Files.updateStorageStatistics();
 						} else {
-							OC.Notification.hide();
 							if (result.status === 'error' && result.data.message) {
 								OC.Notification.show(result.data.message);
 							}
 							else {
-								OC.Notification.show(t('files', 'Error moving file.'));
+								OC.Notification.show(t('files', 'Error deleting file.'));
 							}
 							// hide notification after 10 sec
 							setTimeout(function() {
 								OC.Notification.hide();
 							}, 10000);
-						}
-					} else {
-						OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
-					}
-					$td.css('background-image', oldBackgroundImage);
-				});
-		});
-
-	},
-
-	/**
-	 * Triggers file rename input field for the given file name.
-	 * If the user enters a new name, the file will be renamed.
-	 *
-	 * @param oldname file name of the file to rename
-	 */
-	rename: function(oldname) {
-		var tr, td, input, form;
-		tr = FileList.findFileEl(oldname);
-		var oldFileInfo = this.files[tr.index()];
-		tr.data('renaming',true);
-		td = tr.children('td.filename');
-		input = $('<input type="text" class="filename"/>').val(oldname);
-		form = $('<form></form>');
-		form.append(input);
-		td.children('a.name').hide();
-		td.append(form);
-		input.focus();
-		//preselect input
-		var len = input.val().lastIndexOf('.');
-		if ( len === -1 ||
-			tr.data('type') === 'dir' ) {
-			len = input.val().length;
-		}
-		input.selectRange(0, len);
-		var checkInput = function () {
-			var filename = input.val();
-			if (filename !== oldname) {
-				// Files.isFileNameValid(filename) throws an exception itself
-				Files.isFileNameValid(filename);
-				if (FileList.inList(filename)) {
-					throw t('files', '{new_name} already exists', {new_name: filename});
-				}
-			}
-			return true;
-		};
-
-		form.submit(function(event) {
-			event.stopPropagation();
-			event.preventDefault();
-			try {
-				var newName = input.val();
-				if (newName !== oldname) {
-					checkInput();
-					// mark as loading
-					td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
-					$.ajax({
-						url: OC.filePath('files','ajax','rename.php'),
-						data: {
-							dir : $('#dir').val(),
-							newname: newName,
-							file: oldname
-						},
-						success: function(result) {
-							var fileInfo;
-							if (!result || result.status === 'error') {
-								OC.dialogs.alert(result.data.message, t('core', 'Could not rename file'));
-								fileInfo = oldFileInfo;
+							if (params.allfiles) {
+								// reload the page as we don't know what files were deleted
+								// and which ones remain
+								self.reload();
 							}
 							else {
-								fileInfo = result.data;
+								$.each(files,function(index,file) {
+									var deleteAction = self.findFileEl(file).find('.action.delete');
+									deleteAction.removeClass('progress-icon').addClass('delete-icon');
+								});
 							}
-							// reinsert row
-							FileList.files.splice(tr.index(), 1);
-							tr.remove();
-							FileList.add(fileInfo);
 						}
 					});
-				}
-				input.tipsy('hide');
-				tr.data('renaming',false);
-				tr.attr('data-file', newName);
-				var path = td.children('a.name').attr('href');
-				// FIXME this will fail if the path contains the filename.
-				td.children('a.name').attr('href', path.replace(encodeURIComponent(oldname), encodeURIComponent(newName)));
-				var basename = newName;
-				if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') {
-					basename = newName.substr(0, newName.lastIndexOf('.'));
-				}
-				td.find('a.name span.nametext').text(basename);
-				if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') {
-					if ( ! td.find('a.name span.extension').exists() ) {
-						td.find('a.name span.nametext').append('<span class="extension"></span>');
-					}
-					td.find('a.name span.extension').text(newName.substr(newName.lastIndexOf('.')));
-				}
-				form.remove();
-				FileActions.display( tr.find('td.filename'), true);
-				td.children('a.name').show();
-			} catch (error) {
-				input.attr('title', error);
-				input.tipsy({gravity: 'w', trigger: 'manual'});
-				input.tipsy('show');
-				input.addClass('error');
-			}
-			return false;
-		});
-		input.keyup(function(event) {
-			// verify filename on typing
-			try {
-				checkInput();
-				input.tipsy('hide');
-				input.removeClass('error');
-			} catch (error) {
-				input.attr('title', error);
-				input.tipsy({gravity: 'w', trigger: 'manual'});
-				input.tipsy('show');
-				input.addClass('error');
-			}
-			if (event.keyCode === 27) {
-				input.tipsy('hide');
-				tr.data('renaming',false);
-				form.remove();
-				td.children('a.name').show();
-			}
-		});
-		input.click(function(event) {
-			event.stopPropagation();
-			event.preventDefault();
-		});
-		input.blur(function() {
-			form.trigger('submit');
-		});
-	},
-	inList:function(file) {
-		return FileList.findFileEl(file).length;
-	},
-	/**
-	 * Delete the given files from the given dir
-	 * @param files file names list (without path)
-	 * @param dir directory in which to delete the files, defaults to the current
-	 * directory
-	 */
-	do_delete:function(files, dir) {
-		var params;
-		if (files && files.substr) {
-			files=[files];
-		}
-		if (files) {
-			for (var i=0; i<files.length; i++) {
-				var deleteAction = FileList.findFileEl(files[i]).children("td.date").children(".action.delete");
-				deleteAction.removeClass('delete-icon').addClass('progress-icon');
+		},
+		/**
+		 * Creates the file summary section
+		 */
+		_createSummary: function() {
+			var $tr = $('<tr class="summary"></tr>');
+			this.$el.find('tfoot').append($tr);
+
+			return new OCA.Files.FileSummary($tr);
+		},
+		updateEmptyContent: function() {
+			var permissions = this.getDirectoryPermissions();
+			var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
+			this.$el.find('#emptycontent').toggleClass('hidden', !isCreatable || !this.isEmpty);
+			this.$el.find('#filestable thead th').toggleClass('hidden', this.isEmpty);
+		},
+		/**
+		 * Shows the loading mask.
+		 *
+		 * @see #hideMask
+		 */
+		showMask: function() {
+			// in case one was shown before
+			var $mask = this.$el.find('.mask');
+			if ($mask.exists()) {
+				return;
 			}
-		}
-		// Finish any existing actions
-		if (FileList.lastAction) {
-			FileList.lastAction();
-		}
 
-		params = {
-			dir: dir || FileList.getCurrentDirectory()
-		};
-		if (files) {
-			params.files = JSON.stringify(files);
-		}
-		else {
-			// no files passed, delete all in current dir
-			params.allfiles = true;
-			// show spinner for all files
-			this.$fileList.find('tr>td.date .action.delete').removeClass('delete-icon').addClass('progress-icon');
-		}
-
-		$.post(OC.filePath('files', 'ajax', 'delete.php'),
-				params,
-				function(result) {
-					if (result.status === 'success') {
-						if (params.allfiles) {
-							FileList.setFiles([]);
-						}
-						else {
-							$.each(files,function(index,file) {
-								var fileEl = FileList.remove(file, {updateSummary: false});
-								// FIXME: not sure why we need this after the
-								// element isn't even in the DOM any more
-								fileEl.find('input[type="checkbox"]').prop('checked', false);
-								fileEl.removeClass('selected');
-								FileList.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')});
-							});
-						}
-						// TODO: this info should be returned by the ajax call!
-						checkTrashStatus();
-						FileList.updateEmptyContent();
-						FileList.fileSummary.update();
-						FileList.updateSelectionSummary();
-						Files.updateStorageStatistics();
-					} else {
-						if (result.status === 'error' && result.data.message) {
-							OC.Notification.show(result.data.message);
-						}
-						else {
-							OC.Notification.show(t('files', 'Error deleting file.'));
-						}
-						// hide notification after 10 sec
-						setTimeout(function() {
-							OC.Notification.hide();
-						}, 10000);
-						if (params.allfiles) {
-							// reload the page as we don't know what files were deleted
-							// and which ones remain
-							FileList.reload();
-						}
-						else {
-							$.each(files,function(index,file) {
-								var deleteAction = FileList.findFileEl(file).find('.action.delete');
-								deleteAction.removeClass('progress-icon').addClass('delete-icon');
-							});
-						}
-					}
+			this.$table.addClass('hidden');
+
+			$mask = $('<div class="mask transparent"></div>');
+
+			$mask.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
+			$mask.css('background-repeat', 'no-repeat');
+			this.$el.append($mask);
+
+			$mask.removeClass('transparent');
+		},
+		/**
+		 * Hide the loading mask.
+		 * @see #showMask
+		 */
+		hideMask: function() {
+			this.$el.find('.mask').remove();
+			this.$table.removeClass('hidden');
+		},
+		scrollTo:function(file) {
+			//scroll to and highlight preselected file
+			var $scrollToRow = this.findFileEl(file);
+			if ($scrollToRow.exists()) {
+				$scrollToRow.addClass('searchresult');
+				$(window).scrollTop($scrollToRow.position().top);
+				//remove highlight when hovered over
+				$scrollToRow.one('hover', function() {
+					$scrollToRow.removeClass('searchresult');
 				});
-	},
-	/**
-	 * Creates the file summary section
-	 */
-	_createSummary: function() {
-		var $tr = $('<tr class="summary"></tr>');
-		this.$el.find('tfoot').append($tr);
-
-		return new FileSummary($tr);
-	},
-	updateEmptyContent: function() {
-		var permissions = $('#permissions').val();
-		var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
-		$('#emptycontent').toggleClass('hidden', !isCreatable || !FileList.isEmpty);
-		$('#filestable thead th').toggleClass('hidden', FileList.isEmpty);
-	},
-	/**
-	 * Shows the loading mask.
-	 *
-	 * @see #hideMask
-	 */
-	showMask: function() {
-		// in case one was shown before
-		var $mask = $('#content .mask');
-		if ($mask.exists()) {
-			return;
-		}
-
-		this.$el.addClass('hidden');
-
-		$mask = $('<div class="mask transparent"></div>');
-
-		$mask.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
-		$mask.css('background-repeat', 'no-repeat');
-		$('#content').append($mask);
-
-		$mask.removeClass('transparent');
-	},
-	/**
-	 * Hide the loading mask.
-	 * @see #showMask
-	 */
-	hideMask: function() {
-		$('#content .mask').remove();
-		this.$el.removeClass('hidden');
-	},
-	scrollTo:function(file) {
-		//scroll to and highlight preselected file
-		var $scrollToRow = FileList.findFileEl(file);
-		if ($scrollToRow.exists()) {
-			$scrollToRow.addClass('searchresult');
-			$(window).scrollTop($scrollToRow.position().top);
-			//remove highlight when hovered over
-			$scrollToRow.one('hover', function() {
-				$scrollToRow.removeClass('searchresult');
+			}
+		},
+		filter:function(query) {
+			this.$fileList.find('tr').each(function(i,e) {
+				if ($(e).data('file').toString().toLowerCase().indexOf(query.toLowerCase()) !== -1) {
+					$(e).addClass("searchresult");
+				} else {
+					$(e).removeClass("searchresult");
+				}
 			});
-		}
-	},
-	filter:function(query) {
-		$('#fileList tr').each(function(i,e) {
-			if ($(e).data('file').toString().toLowerCase().indexOf(query.toLowerCase()) !== -1) {
-				$(e).addClass("searchresult");
-			} else {
+			//do not use scrollto to prevent removing searchresult css class
+			var first = this.$fileList.find('tr.searchresult').first();
+			if (first.exists()) {
+				$(window).scrollTop(first.position().top);
+			}
+		},
+		unfilter:function() {
+			this.$fileList.find('tr.searchresult').each(function(i,e) {
 				$(e).removeClass("searchresult");
+			});
+		},
+		/**
+		 * Update UI based on the current selection
+		 */
+		updateSelectionSummary: function() {
+			var summary = this._selectionSummary.summary;
+			if (summary.totalFiles === 0 && summary.totalDirs === 0) {
+				this.$el.find('#headerName a.name>span:first').text(t('files','Name'));
+				this.$el.find('#headerSize a>span:first').text(t('files','Size'));
+				this.$el.find('#modified a>span:first').text(t('files','Modified'));
+				this.$el.find('table').removeClass('multiselect');
+				this.$el.find('.selectedActions').addClass('hidden');
 			}
-		});
-		//do not use scrollto to prevent removing searchresult css class
-		var first = $('#fileList tr.searchresult').first();
-		if (first.exists()) {
-			$(window).scrollTop(first.position().top);
-		}
-	},
-	unfilter:function() {
-		$('#fileList tr.searchresult').each(function(i,e) {
-			$(e).removeClass("searchresult");
-		});
-	},
-	/**
-	 * Update UI based on the current selection
-	 */
-	updateSelectionSummary: function() {
-		var summary = this._selectionSummary.summary;
-		if (summary.totalFiles === 0 && summary.totalDirs === 0) {
-			$('#headerName a.name>span:first').text(t('files','Name'));
-			$('#headerSize a>span:first').text(t('files','Size'));
-			$('#modified a>span:first').text(t('files','Modified'));
-			$('table').removeClass('multiselect');
-			$('.selectedActions').addClass('hidden');
-		}
-		else {
-			$('.selectedActions').removeClass('hidden');
-			$('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize));
-			var selection = '';
-			if (summary.totalDirs > 0) {
-				selection += n('files', '%n folder', '%n folders', summary.totalDirs);
+			else {
+				this.$el.find('.selectedActions').removeClass('hidden');
+				this.$el.find('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize));
+				var selection = '';
+				if (summary.totalDirs > 0) {
+					selection += n('files', '%n folder', '%n folders', summary.totalDirs);
+					if (summary.totalFiles > 0) {
+						selection += ' & ';
+					}
+				}
 				if (summary.totalFiles > 0) {
-					selection += ' & ';
+					selection += n('files', '%n file', '%n files', summary.totalFiles);
 				}
+				this.$el.find('#headerName a.name>span:first').text(selection);
+				this.$el.find('#modified a>span:first').text('');
+				this.$el.find('table').addClass('multiselect');
 			}
-			if (summary.totalFiles > 0) {
-				selection += n('files', '%n file', '%n files', summary.totalFiles);
+		},
+
+		/**
+		 * Returns whether all files are selected
+		 * @return true if all files are selected, false otherwise
+		 */
+		isAllSelected: function() {
+			return this.$el.find('#select_all').prop('checked');
+		},
+
+		/**
+		 * Returns the file info of the selected files
+		 *
+		 * @return array of file names
+		 */
+		getSelectedFiles: function() {
+			return _.values(this._selectedFiles);
+		},
+
+		getUniqueName: function(name) {
+			if (this.findFileEl(name).exists()) {
+				var numMatch;
+				var parts=name.split('.');
+				var extension = "";
+				if (parts.length > 1) {
+					extension=parts.pop();
+				}
+				var base=parts.join('.');
+				numMatch=base.match(/\((\d+)\)/);
+				var num=2;
+				if (numMatch && numMatch.length>0) {
+					num=parseInt(numMatch[numMatch.length-1])+1;
+					base=base.split('(');
+					base.pop();
+					base=$.trim(base.join('('));
+				}
+				name=base+' ('+num+')';
+				if (extension) {
+					name = name+'.'+extension;
+				}
+				// FIXME: ugly recursion
+				return this.getUniqueName(name);
 			}
-			$('#headerName a.name>span:first').text(selection);
-			$('#modified a>span:first').text('');
-			$('table').addClass('multiselect');
-		}
-	},
-
-	/**
-	 * Returns whether all files are selected
-	 * @return true if all files are selected, false otherwise
-	 */
-	isAllSelected: function() {
-		return this.$el.find('#select_all').prop('checked');
-	},
-
-	/**
-	 * Returns the file info of the selected files
-	 *
-	 * @return array of file names
-	 */
-	getSelectedFiles: function() {
-		return _.values(this._selectedFiles);
-	}
-};
-
-$(document).ready(function() {
-	FileList.initialize();
-
-	// handle upload events
-	var fileUploadStart = $('#file_upload_start');
+			return name;
+		},
+	
+		setupUploadEvents: function() {
+			var self = this;
+
+			// handle upload events
+			var fileUploadStart = this.$el.find('#file_upload_start');
+
+			fileUploadStart.on('fileuploaddrop', function(e, data) {
+				OC.Upload.log('filelist handle fileuploaddrop', e, data);
+
+				var dropTarget = $(e.originalEvent.target).closest('tr, .crumb');
+				if (dropTarget && (dropTarget.data('type') === 'dir' || dropTarget.hasClass('crumb'))) { // drag&drop upload to folder
+
+					// remember as context
+					data.context = dropTarget;
+
+					var dir = dropTarget.data('file');
+					// if from file list, need to prepend parent dir
+					if (dir) {
+						var parentDir = self.getCurrentDirectory();
+						if (parentDir[parentDir.length - 1] !== '/') {
+							parentDir += '/';
+						}
+						dir = parentDir + dir;
+					}
+					else{
+						// read full path from crumb
+						dir = dropTarget.data('dir') || '/';
+					}
 
-	fileUploadStart.on('fileuploaddrop', function(e, data) {
-		OC.Upload.log('filelist handle fileuploaddrop', e, data);
+					// update folder in form
+					data.formData = function(form) {
+						return [
+							{name: 'dir', value: dir},
+							{name: 'requesttoken', value: oc_requesttoken},
+							{name: 'file_directory', value: data.files[0].relativePath}
+						];
+					};
+				} else {
+					// cancel uploads to current dir if no permission
+					var isCreatable = (this.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0;
+					if (!isCreatable) {
+						return false;
+					}
+				}
+			});
+			fileUploadStart.on('fileuploadadd', function(e, data) {
+				OC.Upload.log('filelist handle fileuploadadd', e, data);
 
-		var dropTarget = $(e.originalEvent.target).closest('tr, .crumb');
-		if (dropTarget && (dropTarget.data('type') === 'dir' || dropTarget.hasClass('crumb'))) { // drag&drop upload to folder
+				//finish delete if we are uploading a deleted file
+				if (self.deleteFiles && self.deleteFiles.indexOf(data.files[0].name)!==-1) {
+					self.finishDelete(null, true); //delete file before continuing
+				}
 
-			// remember as context
-			data.context = dropTarget;
+				// add ui visualization to existing folder
+				if (data.context && data.context.data('type') === 'dir') {
+					// add to existing folder
+
+					// update upload counter ui
+					var uploadText = data.context.find('.uploadtext');
+					var currentUploads = parseInt(uploadText.attr('currentUploads'), 10);
+					currentUploads += 1;
+					uploadText.attr('currentUploads', currentUploads);
+
+					var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads);
+					if (currentUploads === 1) {
+						var img = OC.imagePath('core', 'loading.gif');
+						data.context.find('td.filename').attr('style','background-image:url('+img+')');
+						uploadText.text(translatedText);
+						uploadText.show();
+					} else {
+						uploadText.text(translatedText);
+					}
+				}
 
-			var dir = dropTarget.data('file');
-			// if from file list, need to prepend parent dir
-			if (dir) {
-				var parentDir = $('#dir').val() || '/';
-				if (parentDir[parentDir.length - 1] !== '/') {
-					parentDir += '/';
+			});
+			/*
+			 * when file upload done successfully add row to filelist
+			 * update counter when uploading to sub folder
+			 */
+			fileUploadStart.on('fileuploaddone', function(e, data) {
+				OC.Upload.log('filelist handle fileuploaddone', e, data);
+
+				var response;
+				if (typeof data.result === 'string') {
+					response = data.result;
+				} else {
+					// fetch response from iframe
+					response = data.result[0].body.innerText;
 				}
-				dir = parentDir + dir;
-			}
-			else{
-				// read full path from crumb
-				dir = dropTarget.data('dir') || '/';
-			}
+				var result=$.parseJSON(response);
+
+				if (typeof result[0] !== 'undefined' && result[0].status === 'success') {
+					var file = result[0];
+					var size = 0;
+
+					if (data.context && data.context.data('type') === 'dir') {
+
+						// update upload counter ui
+						var uploadText = data.context.find('.uploadtext');
+						var currentUploads = parseInt(uploadText.attr('currentUploads'), 10);
+						currentUploads -= 1;
+						uploadText.attr('currentUploads', currentUploads);
+						var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads);
+						if (currentUploads === 0) {
+							var img = OC.imagePath('core', 'filetypes/folder');
+							data.context.find('td.filename').attr('style','background-image:url('+img+')');
+							uploadText.text(translatedText);
+							uploadText.hide();
+						} else {
+							uploadText.text(translatedText);
+						}
 
-			// update folder in form
-			data.formData = function(form) {
-				return [
-					{name: 'dir', value: dir},
-					{name: 'requesttoken', value: oc_requesttoken},
-					{name: 'file_directory', value: data.files[0].relativePath}
-				];
-			};
-		} else {
-			// cancel uploads to current dir if no permission
-			var isCreatable = (FileList.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0;
-			if (!isCreatable) {
-				return false;
-			}
-		}
-	});
-	fileUploadStart.on('fileuploadadd', function(e, data) {
-		OC.Upload.log('filelist handle fileuploadadd', e, data);
+						// update folder size
+						size = parseInt(data.context.data('size'), 10);
+						size += parseInt(file.size, 10);
+						data.context.attr('data-size', size);
+						data.context.find('td.filesize').text(humanFileSize(size));
+					} else {
+						// only append new file if uploaded into the current folder
+						if (file.directory !== '/' && file.directory !== self.getCurrentDirectory()) {
+
+							var fileDirectory = file.directory.replace('/','').replace(/\/$/, "").split('/');
+
+							if (fileDirectory.length === 1) {
+								fileDirectory = fileDirectory[0];
+
+								// Get the directory 
+								var fd = self.findFileEl(fileDirectory);
+								if (fd.length === 0) {
+									var dir = {
+										name: fileDirectory,
+										type: 'dir',
+										mimetype: 'httpd/unix-directory',
+										permissions: file.permissions,
+										size: 0,
+										id: file.parentId
+									};
+									self.add(dir, {insert: true});
+								}
+							} else {
+								fileDirectory = fileDirectory[0];
+							}
+							
+							fileDirectory = self.findFileEl(fileDirectory);
 
-		//finish delete if we are uploading a deleted file
-		if (FileList.deleteFiles && FileList.deleteFiles.indexOf(data.files[0].name)!==-1) {
-			FileList.finishDelete(null, true); //delete file before continuing
-		}
+							// update folder size
+							size = parseInt(fileDirectory.attr('data-size'), 10);
+							size += parseInt(file.size, 10);
+							fileDirectory.attr('data-size', size);
+							fileDirectory.find('td.filesize').text(humanFileSize(size));
 
-		// add ui visualization to existing folder
-		if (data.context && data.context.data('type') === 'dir') {
-			// add to existing folder
-
-			// update upload counter ui
-			var uploadText = data.context.find('.uploadtext');
-			var currentUploads = parseInt(uploadText.attr('currentUploads'), 10);
-			currentUploads += 1;
-			uploadText.attr('currentUploads', currentUploads);
-
-			var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads);
-			if (currentUploads === 1) {
-				var img = OC.imagePath('core', 'loading.gif');
-				data.context.find('td.filename').attr('style','background-image:url('+img+')');
-				uploadText.text(translatedText);
-				uploadText.show();
-			} else {
-				uploadText.text(translatedText);
-			}
-		}
-
-	});
-	/*
-	 * when file upload done successfully add row to filelist
-	 * update counter when uploading to sub folder
-	 */
-	fileUploadStart.on('fileuploaddone', function(e, data) {
-		OC.Upload.log('filelist handle fileuploaddone', e, data);
-
-		var response;
-		if (typeof data.result === 'string') {
-			response = data.result;
-		} else {
-			// fetch response from iframe
-			response = data.result[0].body.innerText;
-		}
-		var result=$.parseJSON(response);
+							return;
+						}
 
-		if (typeof result[0] !== 'undefined' && result[0].status === 'success') {
-			var file = result[0];
-			var size = 0;
+						// add as stand-alone row to filelist
+						size = t('files', 'Pending');
+						if (data.files[0].size>=0) {
+							size=data.files[0].size;
+						}
+						//should the file exist in the list remove it
+						self.remove(file.name);
 
-			if (data.context && data.context.data('type') === 'dir') {
+						// create new file context
+						data.context = self.add(file, {animate: true});
+					}
+				}
+			});
+			fileUploadStart.on('fileuploadstop', function(e, data) {
+				OC.Upload.log('filelist handle fileuploadstop', e, data);
 
-				// update upload counter ui
-				var uploadText = data.context.find('.uploadtext');
-				var currentUploads = parseInt(uploadText.attr('currentUploads'), 10);
-				currentUploads -= 1;
-				uploadText.attr('currentUploads', currentUploads);
-				var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads);
-				if (currentUploads === 0) {
+				//if user pressed cancel hide upload chrome
+				if (data.errorThrown === 'abort') {
+					//cleanup uploading to a dir
+					var uploadText = $('tr .uploadtext');
 					var img = OC.imagePath('core', 'filetypes/folder');
-					data.context.find('td.filename').attr('style','background-image:url('+img+')');
-					uploadText.text(translatedText);
-					uploadText.hide();
-				} else {
-					uploadText.text(translatedText);
+					uploadText.parents('td.filename').attr('style','background-image:url('+img+')');
+					uploadText.fadeOut();
+					uploadText.attr('currentUploads', 0);
 				}
+			});
+			fileUploadStart.on('fileuploadfail', function(e, data) {
+				OC.Upload.log('filelist handle fileuploadfail', e, data);
 
-				// update folder size
-				size = parseInt(data.context.data('size'), 10);
-				size += parseInt(file.size, 10);
-				data.context.attr('data-size', size);
-				data.context.find('td.filesize').text(humanFileSize(size));
-			} else {
-				// only append new file if uploaded into the current folder
-				if (file.directory !== '/' && file.directory !== FileList.getCurrentDirectory()) {
-
-					var fileDirectory = file.directory.replace('/','').replace(/\/$/, "").split('/');
-
-					if (fileDirectory.length === 1) {
-						fileDirectory = fileDirectory[0];
-
-						// Get the directory 
-						var fd = FileList.findFileEl(fileDirectory);
-						if (fd.length === 0) {
-							var dir = {
-								name: fileDirectory,
-								type: 'dir',
-								mimetype: 'httpd/unix-directory',
-								permissions: file.permissions,
-								size: 0,
-								id: file.parentId
-							};
-							FileList.add(dir, {insert: true});
-						}
-					} else {
-						fileDirectory = fileDirectory[0];
-					}
-					
-					fileDirectory = FileList.findFileEl(fileDirectory);
-
-					// update folder size
-					size = parseInt(fileDirectory.attr('data-size'), 10);
-					size += parseInt(file.size, 10);
-					fileDirectory.attr('data-size', size);
-					fileDirectory.find('td.filesize').text(humanFileSize(size));
-
-					return;
+				//if user pressed cancel hide upload chrome
+				if (data.errorThrown === 'abort') {
+					//cleanup uploading to a dir
+					var uploadText = $('tr .uploadtext');
+					var img = OC.imagePath('core', 'filetypes/folder');
+					uploadText.parents('td.filename').attr('style','background-image:url('+img+')');
+					uploadText.fadeOut();
+					uploadText.attr('currentUploads', 0);
 				}
+			});
 
-				// add as stand-alone row to filelist
-				size = t('files', 'Pending');
-				if (data.files[0].size>=0) {
-					size=data.files[0].size;
-				}
-				//should the file exist in the list remove it
-				FileList.remove(file.name);
+		}
+	};
 
-				// create new file context
-				data.context = FileList.add(file, {animate: true});
+	/**
+	 * Sort comparators.
+	 */
+	FileList.Comparators = {
+		/**
+		 * Compares two file infos by name, making directories appear
+		 * first.
+		 *
+		 * @param fileInfo1 file info
+		 * @param fileInfo2 file info
+		 * @return -1 if the first file must appear before the second one,
+		 * 0 if they are identify, 1 otherwise.
+		 */
+		name: function(fileInfo1, fileInfo2) {
+			if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') {
+				return -1;
 			}
+			if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') {
+				return 1;
+			}
+			return fileInfo1.name.localeCompare(fileInfo2.name);
+		},
+		/**
+		 * Compares two file infos by size.
+		 *
+		 * @param fileInfo1 file info
+		 * @param fileInfo2 file info
+		 * @return -1 if the first file must appear before the second one,
+		 * 0 if they are identify, 1 otherwise.
+		 */
+		size: function(fileInfo1, fileInfo2) {
+			return fileInfo1.size - fileInfo2.size;
+		},
+		/**
+		 * Compares two file infos by timestamp.
+		 *
+		 * @param fileInfo1 file info
+		 * @param fileInfo2 file info
+		 * @return -1 if the first file must appear before the second one,
+		 * 0 if they are identify, 1 otherwise.
+		 */
+		mtime: function(fileInfo1, fileInfo2) {
+			return fileInfo1.mtime - fileInfo2.mtime;
 		}
-	});
-	fileUploadStart.on('fileuploadstop', function(e, data) {
-		OC.Upload.log('filelist handle fileuploadstop', e, data);
-
-		//if user pressed cancel hide upload chrome
-		if (data.errorThrown === 'abort') {
-			//cleanup uploading to a dir
-			var uploadText = $('tr .uploadtext');
-			var img = OC.imagePath('core', 'filetypes/folder');
-			uploadText.parents('td.filename').attr('style','background-image:url('+img+')');
-			uploadText.fadeOut();
-			uploadText.attr('currentUploads', 0);
-		}
-	});
-	fileUploadStart.on('fileuploadfail', function(e, data) {
-		OC.Upload.log('filelist handle fileuploadfail', e, data);
-
-		//if user pressed cancel hide upload chrome
-		if (data.errorThrown === 'abort') {
-			//cleanup uploading to a dir
-			var uploadText = $('tr .uploadtext');
-			var img = OC.imagePath('core', 'filetypes/folder');
-			uploadText.parents('td.filename').attr('style','background-image:url('+img+')');
-			uploadText.fadeOut();
-			uploadText.attr('currentUploads', 0);
-		}
-	});
+	};
 
-	$('#notification').hide();
-	$('#notification:first-child').on('click', '.replace', function() {
-		OC.Notification.hide(function() {
-			FileList.replace(
-				$('#notification > span').attr('data-oldName'),
-				$('#notification > span').attr('data-newName'),
-				$('#notification > span').attr('data-isNewFile'));
-		});
-	});
-	$('#notification:first-child').on('click', '.suggest', function() {
-		var file = $('#notification > span').attr('data-oldName');
-		FileList.findFileEl(file).removeClass('hidden');
-		OC.Notification.hide();
-	});
-	$('#notification:first-child').on('click', '.cancel', function() {
-		if ($('#notification > span').attr('data-isNewFile')) {
-			FileList.deleteCanceled = false;
-			FileList.deleteFiles = [$('#notification > span').attr('data-oldName')];
-		}
-	});
-	FileList.useUndo=(window.onbeforeunload)?true:false;
+	OCA.Files.FileList = FileList;
+})();
+
+$(document).ready(function() {
+	// FIXME: unused ?
+	OCA.Files.FileList.useUndo = (window.onbeforeunload)?true:false;
 	$(window).bind('beforeunload', function () {
-		if (FileList.lastAction) {
-			FileList.lastAction();
+		if (OCA.Files.FileList.lastAction) {
+			OCA.Files.FileList.lastAction();
 		}
 	});
 	$(window).unload(function () {
 		$(window).trigger('beforeunload');
 	});
 
-	function decodeQuery(query) {
-		return query.replace(/\+/g, ' ');
-	}
-
-	function parseHashQuery() {
-		var hash = window.location.hash,
-			pos = hash.indexOf('?');
-		if (pos >= 0) {
-			return hash.substr(pos + 1);
-		}
-		return '';
-	}
-
-	function parseCurrentDirFromUrl() {
-		var query = parseHashQuery(),
-			params;
-		// try and parse from URL hash first
-		if (query) {
-			params = OC.parseQueryString(decodeQuery(query));
-		}
-		// else read from query attributes
-		if (!params) {
-			params = OC.parseQueryString(decodeQuery(location.search));
-		}
-		return (params && params.dir) || '/';
-	}
-
-	// disable ajax/history API for public app (TODO: until it gets ported)
-	// fallback to hashchange when no history support
-	if (!window.history.pushState) {
-		$(window).on('hashchange', function() {
-			FileList.changeDirectory(parseCurrentDirFromUrl(), false);
-		});
-	}
-	window.onpopstate = function(e) {
-		var targetDir;
-		if (e.state && e.state.dir) {
-			targetDir = e.state.dir;
-		}
-		else{
-			// read from URL
-			targetDir = parseCurrentDirFromUrl();
-		}
-		if (targetDir) {
-			FileList.changeDirectory(targetDir, false);
-		}
-	};
-
-	$(window).scroll(function(e) {FileList._onScroll(e);});
-
-	var dir = parseCurrentDirFromUrl();
-	// trigger ajax load, deferred to let sub-apps do their overrides first
-	setTimeout(function() {
-		FileList.changeDirectory(dir, false, true);
-	}, 0);
 });
-
-/**
- * Sort comparators.
- */
-FileList.Comparators = {
-	/**
-	 * Compares two file infos by name, making directories appear
-	 * first.
-	 *
-	 * @param fileInfo1 file info
-	 * @param fileInfo2 file info
-	 * @return -1 if the first file must appear before the second one,
-	 * 0 if they are identify, 1 otherwise.
-	 */
-	name: function(fileInfo1, fileInfo2) {
-		if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') {
-			return -1;
-		}
-		if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') {
-			return 1;
-		}
-		return fileInfo1.name.localeCompare(fileInfo2.name);
-	},
-	/**
-	 * Compares two file infos by size.
-	 *
-	 * @param fileInfo1 file info
-	 * @param fileInfo2 file info
-	 * @return -1 if the first file must appear before the second one,
-	 * 0 if they are identify, 1 otherwise.
-	 */
-	size: function(fileInfo1, fileInfo2) {
-		return fileInfo1.size - fileInfo2.size;
-	},
-	/**
-	 * Compares two file infos by timestamp.
-	 *
-	 * @param fileInfo1 file info
-	 * @param fileInfo2 file info
-	 * @return -1 if the first file must appear before the second one,
-	 * 0 if they are identify, 1 otherwise.
-	 */
-	mtime: function(fileInfo1, fileInfo2) {
-		return fileInfo1.mtime - fileInfo2.mtime;
-	}
-};
-
diff --git a/apps/files/js/files.js b/apps/files/js/files.js
index 60e20a6..9ab8d0f 100644
--- a/apps/files/js/files.js
+++ b/apps/files/js/files.js
@@ -8,265 +8,314 @@
  *
  */
 
-/* global OC, t, FileList */
 /* global getURLParameter */
-var Files = {
-	// file space size sync
-	_updateStorageStatistics: function() {
-		Files._updateStorageStatisticsTimeout = null;
-		var currentDir = FileList.getCurrentDirectory(),
-			state = Files.updateStorageStatistics;
-		if (state.dir){
-			if (state.dir === currentDir) {
+/**
+ * Utility class for file related operations
+ */
+(function() {
+	var Files = {
+		// file space size sync
+		_updateStorageStatistics: function() {
+			// FIXME
+			console.warn('FIXME: storage statistics!');
+			return;
+			Files._updateStorageStatisticsTimeout = null;
+			var currentDir = OCA.Files.FileList.getCurrentDirectory(),
+				state = Files.updateStorageStatistics;
+			if (state.dir){
+				if (state.dir === currentDir) {
+					return;
+				}
+				// cancel previous call, as it was for another dir
+				state.call.abort();
+			}
+			state.dir = currentDir;
+			state.call = $.getJSON(OC.filePath('files','ajax','getstoragestats.php') + '?dir=' + encodeURIComponent(currentDir),function(response) {
+				state.dir = null;
+				state.call = null;
+				Files.updateMaxUploadFilesize(response);
+			});
+		},
+		updateStorageStatistics: function(force) {
+			if (!OC.currentUser) {
 				return;
 			}
-			// cancel previous call, as it was for another dir
-			state.call.abort();
-		}
-		state.dir = currentDir;
-		state.call = $.getJSON(OC.filePath('files','ajax','getstoragestats.php') + '?dir=' + encodeURIComponent(currentDir),function(response) {
-			state.dir = null;
-			state.call = null;
-			Files.updateMaxUploadFilesize(response);
-		});
-	},
-	updateStorageStatistics: function(force) {
-		if (!OC.currentUser) {
-			return;
-		}
 
-		// debounce to prevent calling too often
-		if (Files._updateStorageStatisticsTimeout) {
-			clearTimeout(Files._updateStorageStatisticsTimeout);
-		}
-		if (force) {
-			Files._updateStorageStatistics();
-		}
-		else {
-			Files._updateStorageStatisticsTimeout = setTimeout(Files._updateStorageStatistics, 250);
-		}
-	},
+			// debounce to prevent calling too often
+			if (Files._updateStorageStatisticsTimeout) {
+				clearTimeout(Files._updateStorageStatisticsTimeout);
+			}
+			if (force) {
+				Files._updateStorageStatistics();
+			}
+			else {
+				Files._updateStorageStatisticsTimeout = setTimeout(Files._updateStorageStatistics, 250);
+			}
+		},
 
-	updateMaxUploadFilesize:function(response) {
-		if (response === undefined) {
-			return;
-		}
-		if (response.data !== undefined && response.data.uploadMaxFilesize !== undefined) {
-			$('#max_upload').val(response.data.uploadMaxFilesize);
-			$('#free_space').val(response.data.freeSpace);
-			$('#upload.button').attr('original-title', response.data.maxHumanFilesize);
-			$('#usedSpacePercent').val(response.data.usedSpacePercent);
-			Files.displayStorageWarnings();
-		}
-		if (response[0] === undefined) {
-			return;
-		}
-		if (response[0].uploadMaxFilesize !== undefined) {
-			$('#max_upload').val(response[0].uploadMaxFilesize);
-			$('#upload.button').attr('original-title', response[0].maxHumanFilesize);
-			$('#usedSpacePercent').val(response[0].usedSpacePercent);
-			Files.displayStorageWarnings();
-		}
+		updateMaxUploadFilesize:function(response) {
+			if (response === undefined) {
+				return;
+			}
+			if (response.data !== undefined && response.data.uploadMaxFilesize !== undefined) {
+				$('#max_upload').val(response.data.uploadMaxFilesize);
+				$('#free_space').val(response.data.freeSpace);
+				$('#upload.button').attr('original-title', response.data.maxHumanFilesize);
+				$('#usedSpacePercent').val(response.data.usedSpacePercent);
+				Files.displayStorageWarnings();
+			}
+			if (response[0] === undefined) {
+				return;
+			}
+			if (response[0].uploadMaxFilesize !== undefined) {
+				$('#max_upload').val(response[0].uploadMaxFilesize);
+				$('#upload.button').attr('original-title', response[0].maxHumanFilesize);
+				$('#usedSpacePercent').val(response[0].usedSpacePercent);
+				Files.displayStorageWarnings();
+			}
 
-	},
+		},
 
-	/**
-	 * Fix path name by removing double slash at the beginning, if any
-	 */
-	fixPath: function(fileName) {
-		if (fileName.substr(0, 2) == '//') {
-			return fileName.substr(1);
-		}
-		return fileName;
-	},
+		/**
+		 * Fix path name by removing double slash at the beginning, if any
+		 */
+		fixPath: function(fileName) {
+			if (fileName.substr(0, 2) == '//') {
+				return fileName.substr(1);
+			}
+			return fileName;
+		},
+
+		/**
+		 * Checks whether the given file name is valid.
+		 * @param name file name to check
+		 * @return true if the file name is valid.
+		 * Throws a string exception with an error message if
+		 * the file name is not valid
+		 */
+		isFileNameValid: function (name) {
+			var trimmedName = name.trim();
+			if (trimmedName === '.'	|| trimmedName === '..')
+			{
+				throw t('files', '"{name}" is an invalid file name.', {name: name});
+			} else if (trimmedName.length === 0) {
+				throw t('files', 'File name cannot be empty.');
+			}
+			// check for invalid characters
+			var invalidCharacters =
+				['\\', '/', '<', '>', ':', '"', '|', '?', '*', '\n'];
+			for (var i = 0; i < invalidCharacters.length; i++) {
+				if (trimmedName.indexOf(invalidCharacters[i]) !== -1) {
+					throw t('files', "Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed.");
+				}
+			}
+			return true;
+		},
+		displayStorageWarnings: function() {
+			if (!OC.Notification.isHidden()) {
+				return;
+			}
 
-	/**
-	 * Checks whether the given file name is valid.
-	 * @param name file name to check
-	 * @return true if the file name is valid.
-	 * Throws a string exception with an error message if
-	 * the file name is not valid
-	 */
-	isFileNameValid: function (name) {
-		var trimmedName = name.trim();
-		if (trimmedName === '.'	|| trimmedName === '..')
-		{
-			throw t('files', '"{name}" is an invalid file name.', {name: name});
-		} else if (trimmedName.length === 0) {
-			throw t('files', 'File name cannot be empty.');
-		}
-		// check for invalid characters
-		var invalidCharacters =
-			['\\', '/', '<', '>', ':', '"', '|', '?', '*', '\n'];
-		for (var i = 0; i < invalidCharacters.length; i++) {
-			if (trimmedName.indexOf(invalidCharacters[i]) !== -1) {
-				throw t('files', "Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed.");
+			var usedSpacePercent = $('#usedSpacePercent').val();
+			if (usedSpacePercent > 98) {
+				OC.Notification.show(t('files', 'Your storage is full, files can not be updated or synced anymore!'));
+				return;
 			}
-		}
-		return true;
-	},
-	displayStorageWarnings: function() {
-		if (!OC.Notification.isHidden()) {
-			return;
-		}
+			if (usedSpacePercent > 90) {
+				OC.Notification.show(t('files', 'Your storage is almost full ({usedSpacePercent}%)',
+					{usedSpacePercent: usedSpacePercent}));
+			}
+		},
 
-		var usedSpacePercent = $('#usedSpacePercent').val();
-		if (usedSpacePercent > 98) {
-			OC.Notification.show(t('files', 'Your storage is full, files can not be updated or synced anymore!'));
-			return;
-		}
-		if (usedSpacePercent > 90) {
-			OC.Notification.show(t('files', 'Your storage is almost full ({usedSpacePercent}%)',
-				{usedSpacePercent: usedSpacePercent}));
-		}
-	},
+		displayEncryptionWarning: function() {
 
-	displayEncryptionWarning: function() {
+			if (!OC.Notification.isHidden()) {
+				return;
+			}
 
-		if (!OC.Notification.isHidden()) {
-			return;
-		}
+			var encryptedFiles = $('#encryptedFiles').val();
+			var initStatus = $('#encryptionInitStatus').val();
+			if (initStatus === '0') { // enc not initialized, but should be
+				OC.Notification.show(t('files', 'Encryption App is enabled but your keys are not initialized, please log-out and log-in again'));
+				return;
+			}
+			if (initStatus === '1') { // encryption tried to init but failed
+				OC.Notification.show(t('files', 'Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files.'));
+				return;
+			}
+			if (encryptedFiles === '1') {
+				OC.Notification.show(t('files', 'Encryption was disabled but your files are still encrypted. Please go to your personal settings to decrypt your files.'));
+				return;
+			}
+		},
 
-		var encryptedFiles = $('#encryptedFiles').val();
-		var initStatus = $('#encryptionInitStatus').val();
-		if (initStatus === '0') { // enc not initialized, but should be
-			OC.Notification.show(t('files', 'Encryption App is enabled but your keys are not initialized, please log-out and log-in again'));
-			return;
-		}
-		if (initStatus === '1') { // encryption tried to init but failed
-			OC.Notification.show(t('files', 'Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files.'));
-			return;
-		}
-		if (encryptedFiles === '1') {
-			OC.Notification.show(t('files', 'Encryption was disabled but your files are still encrypted. Please go to your personal settings to decrypt your files.'));
-			return;
-		}
-	},
+		// TODO: move to FileList class
+		setupDragAndDrop: function() {
+			var $fileList = $('#fileList');
 
-	// TODO: move to FileList class
-	setupDragAndDrop: function() {
-		var $fileList = $('#fileList');
+			//drag/drop of files
+			$fileList.find('tr td.filename').each(function(i,e) {
+				if ($(e).parent().data('permissions') & OC.PERMISSION_DELETE) {
+					$(e).draggable(dragOptions);
+				}
+			});
 
-		//drag/drop of files
-		$fileList.find('tr td.filename').each(function(i,e) {
-			if ($(e).parent().data('permissions') & OC.PERMISSION_DELETE) {
-				$(e).draggable(dragOptions);
+			$fileList.find('tr[data-type="dir"] td.filename').each(function(i,e) {
+				if ($(e).parent().data('permissions') & OC.PERMISSION_CREATE) {
+					$(e).droppable(folderDropOptions);
+				}
+			});
+		},
+
+		/**
+		 * Returns the download URL of the given file(s)
+		 * @param filename string or array of file names to download
+		 * @param dir optional directory in which the file name is, defaults to the current directory
+		 */
+		getDownloadUrl: function(filename, dir) {
+			if ($.isArray(filename)) {
+				filename = JSON.stringify(filename);
 			}
-		});
-
-		$fileList.find('tr[data-type="dir"] td.filename').each(function(i,e) {
-			if ($(e).parent().data('permissions') & OC.PERMISSION_CREATE) {
-				$(e).droppable(folderDropOptions);
+			var params = {
+				dir: dir,
+				files: filename
+			};
+			return this.getAjaxUrl('download', params);
+		},
+
+		/**
+		 * Returns the ajax URL for a given action
+		 * @param action action string
+		 * @param params optional params map
+		 */
+		getAjaxUrl: function(action, params) {
+			var q = '';
+			if (params) {
+				q = '?' + OC.buildQueryString(params);
 			}
-		});
-	},
+			return OC.filePath('files', 'ajax', action + '.php') + q;
+		},
+
+		getMimeIcon: function(mime, ready) {
+			if (Files.getMimeIcon.cache[mime]) {
+				ready(Files.getMimeIcon.cache[mime]);
+			} else {
+				$.get( OC.filePath('files','ajax','mimeicon.php'), {mime: mime}, function(path) {
+					if(OC.Util.hasSVGSupport()){
+						path = path.substr(0, path.length-4) + '.svg';
+					}
+					Files.getMimeIcon.cache[mime]=path;
+					ready(Files.getMimeIcon.cache[mime]);
+				});
+			}
+		},
+
+		/**
+		 * Generates a preview URL based on the URL space.
+		 * @param urlSpec map with {x: width, y: height, file: file path}
+		 * @return preview URL
+		 * @deprecated used OCA.Files.FileList.generatePreviewUrl instead
+		 */
+		generatePreviewUrl: function(urlSpec) {
+			console.warn('DEPRECATED: please use generatePreviewUrl() from an OCA.Files.FileList instance');
+			return OCA.Files.App.fileList.generatePreviewUrl(urlSpec);
+		},
+
+		/**
+		 * Lazy load preview
+		 * @deprecated used OCA.Files.FileList.lazyLoadPreview instead
+		 */
+		lazyLoadPreview : function(path, mime, ready, width, height, etag) {
+			console.warn('DEPRECATED: please use lazyLoadPreview() from an OCA.Files.FileList instance');
+			return OCA.Files.App.fileList.lazyLoadPreview({
+				path: path,
+				mime: mime,
+				callback: ready,
+				width: width,
+				height: height,
+				etag: etag
+			});
+		},
 
-	/**
-	 * Returns the download URL of the given file(s)
-	 * @param filename string or array of file names to download
-	 * @param dir optional directory in which the file name is, defaults to the current directory
-	 */
-	getDownloadUrl: function(filename, dir) {
-		if ($.isArray(filename)) {
-			filename = JSON.stringify(filename);
-		}
-		var params = {
-			dir: dir || FileList.getCurrentDirectory(),
-			files: filename
-		};
-		return this.getAjaxUrl('download', params);
-	},
+		/**
+		 * Initialize the files view
+		 */
+		initialize: function() {
+			Files.getMimeIcon.cache = {};
+			Files.displayEncryptionWarning();
+			Files.bindKeyboardShortcuts(document, jQuery);
 
-	/**
-	 * Returns the ajax URL for a given action
-	 * @param action action string
-	 * @param params optional params map
-	 */
-	getAjaxUrl: function(action, params) {
-		var q = '';
-		if (params) {
-			q = '?' + OC.buildQueryString(params);
-		}
-		return OC.filePath('files', 'ajax', action + '.php') + q;
-	}
-};
-$(document).ready(function() {
-	// FIXME: workaround for trashbin app
-	if (window.trashBinApp) {
-		return;
-	}
-	Files.displayEncryptionWarning();
-	Files.bindKeyboardShortcuts(document, jQuery);
+			Files.setupDragAndDrop();
 
-	Files.setupDragAndDrop();
+			// TODO: move file list related code (upload) to OCA.Files.FileList
+			$('#file_action_panel').attr('activeAction', false);
 
-	$('#file_action_panel').attr('activeAction', false);
-
-	// Triggers invisible file input
-	$('#upload a').on('click', function() {
-		$(this).parent().children('#file_upload_start').trigger('click');
-		return false;
-	});
+			// Triggers invisible file input
+			$('#upload a').on('click', function() {
+				$(this).parent().children('#file_upload_start').trigger('click');
+				return false;
+			});
 
-	// Trigger cancelling of file upload
-	$('#uploadprogresswrapper .stop').on('click', function() {
-		OC.Upload.cancelUploads();
-		FileList.updateSelectionSummary();
-	});
+			// Trigger cancelling of file upload
+			$('#uploadprogresswrapper .stop').on('click', function() {
+				OC.Upload.cancelUploads();
+				OCA.Files.FileList.updateSelectionSummary();
+			});
 
-	// Show trash bin
-	$('#trash').on('click', function() {
-		window.location=OC.filePath('files_trashbin', '', 'index.php');
-	});
+			// drag&drop support using jquery.fileupload
+			// TODO use OC.dialogs
+			$(document).bind('drop dragover', function (e) {
+					e.preventDefault(); // prevent browser from doing anything, if file isn't dropped in dropZone
+				});
+
+			//do a background scan if needed
+			scanFiles();
+
+			// display storage warnings
+			setTimeout(Files.displayStorageWarnings, 100);
+			OC.Notification.setDefault(Files.displayStorageWarnings);
+
+			// only possible at the moment if user is logged in
+			if (OC.currentUser) {
+				// start on load - we ask the server every 5 minutes
+				var updateStorageStatisticsInterval = 5*60*1000;
+				var updateStorageStatisticsIntervalId = setInterval(Files.updateStorageStatistics, updateStorageStatisticsInterval);
+
+				// Use jquery-visibility to de-/re-activate file stats sync
+				if ($.support.pageVisibility) {
+					$(document).on({
+						'show.visibility': function() {
+							if (!updateStorageStatisticsIntervalId) {
+								updateStorageStatisticsIntervalId = setInterval(Files.updateStorageStatistics, updateStorageStatisticsInterval);
+							}
+						},
+						'hide.visibility': function() {
+							clearInterval(updateStorageStatisticsIntervalId);
+							updateStorageStatisticsIntervalId = 0;
+						}
+					});
+				}
+			}
 
-	// drag&drop support using jquery.fileupload
-	// TODO use OC.dialogs
-	$(document).bind('drop dragover', function (e) {
-			e.preventDefault(); // prevent browser from doing anything, if file isn't dropped in dropZone
-		});
-
-	//do a background scan if needed
-	scanFiles();
-
-	// display storage warnings
-	setTimeout(Files.displayStorageWarnings, 100);
-	OC.Notification.setDefault(Files.displayStorageWarnings);
-
-	// only possible at the moment if user is logged in
-	if (OC.currentUser) {
-		// start on load - we ask the server every 5 minutes
-		var updateStorageStatisticsInterval = 5*60*1000;
-		var updateStorageStatisticsIntervalId = setInterval(Files.updateStorageStatistics, updateStorageStatisticsInterval);
-
-		// Use jquery-visibility to de-/re-activate file stats sync
-		if ($.support.pageVisibility) {
-			$(document).on({
-				'show.visibility': function() {
-					if (!updateStorageStatisticsIntervalId) {
-						updateStorageStatisticsIntervalId = setInterval(Files.updateStorageStatistics, updateStorageStatisticsInterval);
-					}
-				},
-				'hide.visibility': function() {
-					clearInterval(updateStorageStatisticsIntervalId);
-					updateStorageStatisticsIntervalId = 0;
+			$('#app-settings-header').on('click', function() {
+				var $settings = $('#app-settings');
+				$settings.toggleClass('opened');
+				if ($settings.hasClass('opened')) {
+					$settings.find('input').focus();
 				}
 			});
-		}
-	}
 
-	$('#app-settings-header').on('click', function() {
-		var $settings = $('#app-settings');
-		$settings.toggleClass('opened');
-		if ($settings.hasClass('opened')) {
-			$settings.find('input').focus();
+			//scroll to and highlight preselected file
+			/*
+			if (getURLParameter('scrollto')) {
+				FileList.scrollTo(getURLParameter('scrollto'));
+			}
+			*/
 		}
-	});
-
-	//scroll to and highlight preselected file
-	if (getURLParameter('scrollto')) {
-		FileList.scrollTo(getURLParameter('scrollto'));
 	}
-});
+
+	OCA.Files.Files = Files;
+})();
 
 function scanFiles(force, dir, users) {
 	if (!OC.currentUser) {
@@ -300,7 +349,7 @@ function scanFiles(force, dir, users) {
 	scannerEventSource.listen('done',function(count) {
 		scanFiles.scanning=false;
 		console.log('done after ' + count + ' files');
-		Files.updateStorageStatistics();
+		OCA.Files.Files.updateStorageStatistics();
 	});
 	scannerEventSource.listen('user',function(user) {
 		console.log('scanning files for ' + user);
@@ -311,6 +360,7 @@ scanFiles.scanning=false;
 // TODO: move to FileList
 var createDragShadow = function(event) {
 	//select dragged file
+	var FileList = OCA.Files.FileList;
 	var isDragSelected = $(event.target).parents('tr').find('td input:first').prop('checked');
 	if (!isDragSelected) {
 		//select dragged file
@@ -331,7 +381,7 @@ var createDragShadow = function(event) {
 	var tbody = $('<tbody></tbody>');
 	dragshadow.append(tbody);
 
-	var dir=$('#dir').val();
+	var dir = FileList.getCurrentDirectory();
 
 	$(selectedFiles).each(function(i,elem) {
 		var newtr = $('<tr/>')
@@ -344,8 +394,8 @@ var createDragShadow = function(event) {
 		if (elem.type === 'dir') {
 			newtr.find('td.filename').attr('style','background-image:url('+OC.imagePath('core', 'filetypes/folder.png')+')');
 		} else {
-			var path = getPathForPreview(elem.name);
-			Files.lazyLoadPreview(path, elem.mime, function(previewpath) {
+			var path = dir + '/' + elem.name;
+			OCA.Files.App.fileList.lazyLoadPreview(path, elem.mime, function(previewpath) {
 				newtr.find('td.filename').attr('style','background-image:url('+previewpath+')');
 			}, null, null, elem.etag);
 		}
@@ -358,9 +408,14 @@ var createDragShadow = function(event) {
 //start&stop handlers needs some cleaning up
 // TODO: move to FileList class
 var dragOptions={
-	revert: 'invalid', revertDuration: 300,
-	opacity: 0.7, zIndex: 100, appendTo: 'body', cursorAt: { left: 24, top: 18 },
-	helper: createDragShadow, cursor: 'move',
+	revert: 'invalid',
+	revertDuration: 300,
+	opacity: 0.7,
+	zIndex: 100,
+	appendTo: 'body',
+	cursorAt: { left: 24, top: 18 },
+	helper: createDragShadow,
+	cursor: 'move',
 	start: function(event, ui){
 		var $selectedFiles = $('td.filename input:checkbox:checked');
 		if($selectedFiles.length > 1){
@@ -391,6 +446,7 @@ var folderDropOptions = {
 	hoverClass: "canDrop",
 	drop: function( event, ui ) {
 		// don't allow moving a file into a selected folder
+		var FileList = OCA.Files.FileList;
 		if ($(event.target).parents('tr').find('td input:first').prop('checked') === true) {
 			return false;
 		}
@@ -408,115 +464,11 @@ var folderDropOptions = {
 	tolerance: 'pointer'
 };
 
-Files.getMimeIcon = function(mime, ready) {
-	if (Files.getMimeIcon.cache[mime]) {
-		ready(Files.getMimeIcon.cache[mime]);
-	} else {
-		$.get( OC.filePath('files','ajax','mimeicon.php'), {mime: mime}, function(path) {
-			if(OC.Util.hasSVGSupport()){
-				path = path.substr(0, path.length-4) + '.svg';
-			}
-			Files.getMimeIcon.cache[mime]=path;
-			ready(Files.getMimeIcon.cache[mime]);
-		});
-	}
-}
-Files.getMimeIcon.cache={};
-
-function getPathForPreview(name) {
-	var path = $('#dir').val() + '/' + name;
-	return path;
-}
-
-/**
- * Generates a preview URL based on the URL space.
- * @param urlSpec map with {x: width, y: height, file: file path}
- * @return preview URL
- */
-Files.generatePreviewUrl = function(urlSpec) {
-	urlSpec = urlSpec || {};
-	if (!urlSpec.x) {
-		urlSpec.x = $('#filestable').data('preview-x');
-	}
-	if (!urlSpec.y) {
-		urlSpec.y = $('#filestable').data('preview-y');
-	}
-	urlSpec.y *= window.devicePixelRatio;
-	urlSpec.x *= window.devicePixelRatio;
-	urlSpec.forceIcon = 0;
-	return OC.generateUrl('/core/preview.png?') + $.param(urlSpec);
-};
-
-Files.lazyLoadPreview = function(path, mime, ready, width, height, etag) {
-	// get mime icon url
-	Files.getMimeIcon(mime, function(iconURL) {
-		var previewURL,
-			urlSpec = {};
-		ready(iconURL); // set mimeicon URL
-
-		urlSpec.file = Files.fixPath(path);
-
-		if (etag){
-			// use etag as cache buster
-			urlSpec.c = etag;
-		}
-		else {
-			console.warn('Files.lazyLoadPreview(): missing etag argument');
-		}
-
-		previewURL = Files.generatePreviewUrl(urlSpec);
-		previewURL = previewURL.replace('(', '%28');
-		previewURL = previewURL.replace(')', '%29');
-
-		// preload image to prevent delay
-		// this will make the browser cache the image
-		var img = new Image();
-		img.onload = function(){
-			// if loading the preview image failed (no preview for the mimetype) then img.width will < 5
-			if (img.width > 5) {
-				ready(previewURL);
-			}
-		};
-		img.src = previewURL;
-	});
-};
-
-function getUniqueName(name) {
-	if (FileList.findFileEl(name).exists()) {
-		var numMatch;
-		var parts=name.split('.');
-		var extension = "";
-		if (parts.length > 1) {
-			extension=parts.pop();
-		}
-		var base=parts.join('.');
-		numMatch=base.match(/\((\d+)\)/);
-		var num=2;
-		if (numMatch && numMatch.length>0) {
-			num=parseInt(numMatch[numMatch.length-1])+1;
-			base=base.split('(');
-			base.pop();
-			base=$.trim(base.join('('));
-		}
-		name=base+' ('+num+')';
-		if (extension) {
-			name = name+'.'+extension;
-		}
-		return getUniqueName(name);
-	}
-	return name;
-}
-
-function checkTrashStatus() {
-	$.post(OC.filePath('files_trashbin', 'ajax', 'isEmpty.php'), function(result) {
-		if (result.data.isEmpty === false) {
-			$("input[type=button][id=trash]").removeAttr("disabled");
-		}
-	});
-}
-
 // override core's fileDownloadPath (legacy)
 function fileDownloadPath(dir, file) {
-	return Files.getDownloadUrl(file, dir);
+	return OCA.Files.Files.getDownloadUrl(file, dir);
 }
 
+// for backward compatibility
+window.Files = OCA.Files.Files;
+
diff --git a/apps/files/js/filesummary.js b/apps/files/js/filesummary.js
index b513024..104dabf 100644
--- a/apps/files/js/filesummary.js
+++ b/apps/files/js/filesummary.js
@@ -190,6 +190,6 @@
 			this.$el.append($summary);
 		}
 	};
-	window.FileSummary = FileSummary;
+	OCA.Files.FileSummary = FileSummary;
 })();
 
diff --git a/apps/files/js/navigation.js b/apps/files/js/navigation.js
index f53abdd..c4a02ee 100644
--- a/apps/files/js/navigation.js
+++ b/apps/files/js/navigation.js
@@ -22,7 +22,7 @@
 		/**
 		 * Currently selected item in the list
 		 */
-		_selectedItem: null,
+		_activeItem: null,
 
 		/**
 		 * Currently selected container
@@ -35,7 +35,7 @@
 		 */
 		initialize: function($el) {
 			this.$el = $el;
-			this._selectedItem = null;
+			this._activeItem = null;
 			this.$currentContent = null;
 			this._setupEvents();
 		},
@@ -48,22 +48,47 @@
 		},
 
 		/**
+		 * Returns the container of the currently active app.
+		 *
+		 * @return app container
+		 */
+		getActiveContainer: function() {
+			return this.$currentContent;
+		},
+
+		/**
+		 * Returns the currently active item
+		 * 
+		 * @return item ID
+		 */
+		getActiveItem: function() {
+			return this._activeItem;
+		},
+
+		/**
 		 * Switch the currently selected item, mark it as selected and
 		 * make the content container visible, if any.
+		 *
 		 * @param string itemId id of the navigation item to select
+		 * @param array options "silent" to not trigger event
 		 */
-		setSelectedItem: function(itemId) {
-			if (itemId === this._selectedItem) {
+		setActiveItem: function(itemId, options) {
+			if (itemId === this._activeItem) {
 				return;
 			}
-			this._selectedItem = itemId;
+			this._activeItem = itemId;
 			this.$el.find('li').removeClass('selected');
 			if (this.$currentContent) {
 				this.$currentContent.addClass('hidden');
+				this.$currentContent.trigger(jQuery.Event('hide'));
 			}
 			this.$currentContent = $('#app-content-' + itemId);
 			this.$currentContent.removeClass('hidden');
 			this.$el.find('li[data-id=' + itemId + ']').addClass('selected');
+			if (!options || !options.silent) {
+				this.$currentContent.trigger(jQuery.Event('show'));
+				this.$el.trigger(new $.Event('itemChanged', {itemId: itemId}));
+			}
 		},
 
 		/**
@@ -72,7 +97,8 @@
 		_onClickItem: function(ev) {
 			var $target = $(ev.target);
 			var itemId = $target.closest('li').attr('data-id');
-			this.setSelectedItem(itemId);
+			this.setActiveItem(itemId);
+			return false;
 		}
 	};
 
diff --git a/apps/files/list.php b/apps/files/list.php
new file mode 100644
index 0000000..78b1b07
--- /dev/null
+++ b/apps/files/list.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * ownCloud - Files list
+ *
+ * @author Vincent Petry
+ * @copyright 2014 Vincent Petry <pvince81 at owncloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+// Check if we are a user
+OCP\User::checkLoggedIn();
+
+// dummy, will be refreshed with an ajax call
+$dir = '/';
+
+// information about storage capacities
+// FIXME: storage info
+/*
+$storageInfo=OC_Helper::getStorageInfo($dir, $dirInfo);
+$publicUploadEnabled = $config->getAppValue('core', 'shareapi_allow_public_upload', 'yes');
+$uploadLimit=OCP\Util::uploadLimit();
+$maxUploadFilesize=OCP\Util::maxUploadFilesize($dir, $freeSpace);
+$freeSpace=$storageInfo['free'];
+*/
+
+$tmpl = new OCP\Template('files', 'list', '');
+$tmpl->assign('usedSpacePercent', (int)$storageInfo['relative']);
+$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); // minimium of freeSpace and uploadLimit
+$tmpl->assign('uploadMaxHumanFilesize', OCP\Util::humanFileSize($maxUploadFilesize));
+$tmpl->assign('uploadLimit', $uploadLimit); // PHP upload limit
+$tmpl->assign('freeSpace', $freeSpace);
+$tmpl->assign('publicUploadEnabled', $publicUploadEnabled);
+$tmpl->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true)));
+$tmpl->printPage();
+
diff --git a/apps/files/templates/appnavigation.php b/apps/files/templates/appnavigation.php
index 52e4284..86436bb 100644
--- a/apps/files/templates/appnavigation.php
+++ b/apps/files/templates/appnavigation.php
@@ -1,7 +1,5 @@
 <div id="app-navigation">
 	<ul>
-		<li data-id="files" class="nav-allfiles"><a href="#"><?php p($l->t('All Files'));?></a></li>
-		<li class="sep"></li>
 		<?php foreach ($_['navigationItems'] as $item) { ?>
 		<li data-id="<?php p($item['id']) ?>" class="nav-<?php p($item['id']) ?>"><a href="<?php p(isset($item['href']) ? $item['href'] : '#') ?>"><?php p($item['name']);?></a></li>
 		<?php } ?>
diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php
index e93e931..ce065a9 100644
--- a/apps/files/templates/index.php
+++ b/apps/files/templates/index.php
@@ -1,117 +1,6 @@
 <?php /** @var $l OC_L10N */ ?>
 <?php $_['appNavigation']->printPage(); ?>
 <div id="app-content">
-<div id="app-content-files">
-<div id="controls">
-		<div class="actions creatable hidden">
-			<?php if(!isset($_['dirToken'])):?>
-			<div id="new" class="button">
-				<a><?php p($l->t('New'));?></a>
-				<ul>
-					<li class="icon-filetype-text svg"
-						data-type="file" data-newname="<?php p($l->t('New text file')) ?>.txt">
-						<p><?php p($l->t('Text file'));?></p>
-					</li>
-					<li class="icon-filetype-folder svg"
-						data-type="folder" data-newname="<?php p($l->t('New folder')) ?>">
-						<p><?php p($l->t('Folder'));?></p>
-					</li>
-					<li class="icon-link svg" data-type="web">
-						<p><?php p($l->t('From link'));?></p>
-					</li>
-				</ul>
-			</div>
-			<?php endif;?>
-			<div id="upload" class="button"
-				 title="<?php p($l->t('Upload (max. %s)', array($_['uploadMaxHumanFilesize']))) ?>">
-					<?php if($_['uploadMaxFilesize'] >= 0):?>
-					<input type="hidden" id="max_upload" name="MAX_FILE_SIZE" value="<?php p($_['uploadMaxFilesize']) ?>">
-					<?php endif;?>
-					<input type="hidden" id="upload_limit" value="<?php p($_['uploadLimit']) ?>">
-					<input type="hidden" id="free_space" value="<?php p($_['freeSpace']) ?>">
-					<?php if(isset($_['dirToken'])):?>
-					<input type="hidden" id="publicUploadRequestToken" name="requesttoken" value="<?php p($_['requesttoken']) ?>" />
-					<input type="hidden" id="dirToken" name="dirToken" value="<?php p($_['dirToken']) ?>" />
-					<?php endif;?>
-					<input type="hidden" class="max_human_file_size"
-						   value="(max <?php p($_['uploadMaxHumanFilesize']); ?>)">
-					<input type="hidden" name="dir" value="<?php p($_['dir']) ?>" id="dir">
-					<input type="file" id="file_upload_start" name='files[]'
-						   data-url="<?php print_unescaped(OCP\Util::linkTo('files', 'ajax/upload.php')); ?>" />
-					<a href="#" class="svg icon-upload"></a>
-			</div>
-			<div id="uploadprogresswrapper">
-				<div id="uploadprogressbar"></div>
-				<input type="button" class="stop" style="display:none"
-					value="<?php p($l->t('Cancel upload'));?>"
-				/>
-			</div>
-		</div>
-		<div id="file_action_panel"></div>
-		<div class="notCreatable notPublic hidden">
-			<?php p($l->t('You don’t have permission to upload or create files here'))?>
-		</div>
-	<input type="hidden" name="permissions" value="<?php p($_['permissions']); ?>" id="permissions">
-</div>
-
-<div id="emptycontent" class="hidden"><?php p($l->t('Nothing in here. Upload something!'))?></div>
-
-<input type="hidden" id="disableSharing" data-status="<?php p($_['disableSharing']); ?>" />
-
-<table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36">
-	<thead>
-		<tr>
-			<th id='headerName' class="hidden column-name">
-				<div id="headerName-container">
-					<input type="checkbox" id="select_all" />
-					<label for="select_all"></label>
-					<a class="name sort columntitle" data-sort="name"><span><?php p($l->t( 'Name' )); ?></span><span class="sort-indicator"></span></a>
-					<span id="selectedActionsList" class="selectedActions">
-						<?php if($_['allowZipDownload']) : ?>
-							<a href="" class="download">
-								<img class="svg" alt="Download"
-									 src="<?php print_unescaped(OCP\image_path("core", "actions/download.svg")); ?>" />
-								<?php p($l->t('Download'))?>
-							</a>
-						<?php endif; ?>
-					</span>
-				</div>
-			</th>
-			<th id="headerSize" class="hidden column-size">
-				<a class="size sort columntitle" data-sort="size"><span><?php p($l->t('Size')); ?></span><span class="sort-indicator"></span></a>
-			</th>
-			<th id="headerDate" class="hidden column-mtime">
-				<a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Modified' )); ?></span><span class="sort-indicator"></span></a>
-				<?php if ($_['permissions'] & OCP\PERMISSION_DELETE): ?>
-					<span class="selectedActions"><a href="" class="delete-selected">
-						<?php p($l->t('Delete'))?>
-						<img class="svg" alt="<?php p($l->t('Delete'))?>"
-							 src="<?php print_unescaped(OCP\image_path("core", "actions/delete.svg")); ?>" />
-					</a></span>
-				<?php endif; ?>
-			</th>
-		</tr>
-	</thead>
-	<tbody id="fileList">
-	</tbody>
-	<tfoot>
-	</tfoot>
-</table>
-<div id="editor"></div><!-- FIXME Do not use this div in your app! It is deprecated and will be removed in the future! -->
-<div id="uploadsize-message" title="<?php p($l->t('Upload too large'))?>">
-	<p>
-	<?php p($l->t('The files you are trying to upload exceed the maximum size for file uploads on this server.'));?>
-	</p>
-</div>
-<div id="scanning-message">
-	<h3>
-		<?php p($l->t('Files are being scanned, please wait.'));?> <span id='scan-count'></span>
-	</h3>
-	<p>
-		<?php p($l->t('Current scanning'));?> <span id='scan-current'></span>
-	</p>
-</div>
-</div><!-- closing app-content-files -->
 	<?php foreach ($_['appContents'] as $content) { ?>
 	<div id="app-content-<?php p($content['id']) ?>" class="hidden">
 	<?php print_unescaped($content['content']) ?>
diff --git a/apps/files/templates/index.php b/apps/files/templates/list.php
similarity index 72%
copy from apps/files/templates/index.php
copy to apps/files/templates/list.php
index e93e931..a11defa 100644
--- a/apps/files/templates/index.php
+++ b/apps/files/templates/list.php
@@ -1,7 +1,3 @@
-<?php /** @var $l OC_L10N */ ?>
-<?php $_['appNavigation']->printPage(); ?>
-<div id="app-content">
-<div id="app-content-files">
 <div id="controls">
 		<div class="actions creatable hidden">
 			<?php if(!isset($_['dirToken'])):?>
@@ -35,7 +31,6 @@
 					<?php endif;?>
 					<input type="hidden" class="max_human_file_size"
 						   value="(max <?php p($_['uploadMaxHumanFilesize']); ?>)">
-					<input type="hidden" name="dir" value="<?php p($_['dir']) ?>" id="dir">
 					<input type="file" id="file_upload_start" name='files[]'
 						   data-url="<?php print_unescaped(OCP\Util::linkTo('files', 'ajax/upload.php')); ?>" />
 					<a href="#" class="svg icon-upload"></a>
@@ -51,13 +46,11 @@
 		<div class="notCreatable notPublic hidden">
 			<?php p($l->t('You don’t have permission to upload or create files here'))?>
 		</div>
-	<input type="hidden" name="permissions" value="<?php p($_['permissions']); ?>" id="permissions">
+	<input type="hidden" name="permissions" value="" id="permissions">
 </div>
 
 <div id="emptycontent" class="hidden"><?php p($l->t('Nothing in here. Upload something!'))?></div>
 
-<input type="hidden" id="disableSharing" data-status="<?php p($_['disableSharing']); ?>" />
-
 <table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36">
 	<thead>
 		<tr>
@@ -82,13 +75,11 @@
 			</th>
 			<th id="headerDate" class="hidden column-mtime">
 				<a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Modified' )); ?></span><span class="sort-indicator"></span></a>
-				<?php if ($_['permissions'] & OCP\PERMISSION_DELETE): ?>
 					<span class="selectedActions"><a href="" class="delete-selected">
 						<?php p($l->t('Delete'))?>
 						<img class="svg" alt="<?php p($l->t('Delete'))?>"
 							 src="<?php print_unescaped(OCP\image_path("core", "actions/delete.svg")); ?>" />
 					</a></span>
-				<?php endif; ?>
 			</th>
 		</tr>
 	</thead>
@@ -111,21 +102,3 @@
 		<?php p($l->t('Current scanning'));?> <span id='scan-current'></span>
 	</p>
 </div>
-</div><!-- closing app-content-files -->
-	<?php foreach ($_['appContents'] as $content) { ?>
-	<div id="app-content-<?php p($content['id']) ?>" class="hidden">
-	<?php print_unescaped($content['content']) ?>
-	</div>
-	<?php } ?>
-</div><!-- closing app-content -->
-
-<!-- config hints for javascript -->
-<input type="hidden" name="filesApp" id="filesApp" value="1" />
-<input type="hidden" name="allowZipDownload" id="allowZipDownload" value="<?php p($_['allowZipDownload']); ?>" />
-<input type="hidden" name="usedSpacePercent" id="usedSpacePercent" value="<?php p($_['usedSpacePercent']); ?>" />
-<?php if (!$_['isPublic']) :?>
-<input type="hidden" name="encryptedFiles" id="encryptedFiles" value="<?php $_['encryptedFiles'] ? p('1') : p('0'); ?>" />
-<input type="hidden" name="encryptedInitStatus" id="encryptionInitStatus" value="<?php p($_['encryptionInitStatus']) ?>" />
-<input type="hidden" name="mailNotificationEnabled" id="mailNotificationEnabled" value="<?php p($_['mailNotificationEnabled']) ?>" />
-<input type="hidden" name="allowShareWithLink" id="allowShareWithLink" value="<?php p($_['allowShareWithLink']) ?>" />
-<?php endif;
diff --git a/apps/files/tests/js/appSpec.js b/apps/files/tests/js/appSpec.js
new file mode 100644
index 0000000..315f11f
--- /dev/null
+++ b/apps/files/tests/js/appSpec.js
@@ -0,0 +1,169 @@
+/**
+* ownCloud
+*
+* @author Vincent Petry
+* @copyright 2014 Vincent Petry <pvince81 at owncloud.com>
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+*
+* You should have received a copy of the GNU Affero General Public
+* License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+describe('OCA.Files.App tests', function() {
+	var App = OCA.Files.App;
+	var pushStateStub;
+	var parseUrlQueryStub;
+
+	beforeEach(function() {
+		$('#testArea').append(
+			'<div id="app-navigation">' +
+			'<ul><li data-id="files"><a>Files</a></li>' +
+			'<li data-id="other"><a>Other</a></li>' +
+			'</div>' +
+			'<div id="app-content">' +
+			'<div id="app-content-files" class="hidden">' +
+			'</div>' +
+			'<div id="app-content-other" class="hidden">' +
+			'</div>' +
+			'</div>' +
+			'</div>'
+		);
+
+		pushStateStub = sinon.stub(OC.Util.History, 'pushState');
+		parseUrlQueryStub = sinon.stub(OC.Util.History, 'parseUrlQuery');
+		parseUrlQueryStub.returns({});
+
+		App.initialize();
+	});
+	afterEach(function() {
+		App.navigation = null;
+		App.fileList = null;
+		App.files = null;
+		App.fileActions.clear();
+		App.fileActions = null;
+
+		pushStateStub.restore();
+		parseUrlQueryStub.restore();
+	});
+
+	describe('initialization', function() {
+		it('initializes the default file list with the default file actions', function() {
+			expect(App.fileList).toBeDefined();
+			expect(App.fileList.fileActions.actions.all).toBeDefined();
+			expect(App.fileList.$el.is('#app-content-files')).toEqual(true);
+		});
+	});
+
+	describe('URL handling', function() {
+		it('pushes the state to the URL when current app changed directory', function() {
+			$('#app-content-files').trigger(new $.Event('changeDirectory', {dir: 'subdir'}));
+			expect(pushStateStub.calledOnce).toEqual(true);
+			expect(pushStateStub.getCall(0).args[0].dir).toEqual('subdir');
+			expect(pushStateStub.getCall(0).args[0].view).not.toBeDefined();
+
+			$('li[data-id=other]>a').click();
+			pushStateStub.reset();
+
+			$('#app-content-other').trigger(new $.Event('changeDirectory', {dir: 'subdir'}));
+			expect(pushStateStub.calledOnce).toEqual(true);
+			expect(pushStateStub.getCall(0).args[0].dir).toEqual('subdir');
+			expect(pushStateStub.getCall(0).args[0].view).toEqual('other');
+		});
+		describe('onpopstate', function() {
+			it('sends "urlChanged" event to current app', function() {
+				var handler = sinon.stub();
+				$('#app-content-files').on('urlChanged', handler);
+				App._onPopState({view: 'files', dir: '/somedir'});
+				expect(handler.calledOnce).toEqual(true);
+				expect(handler.getCall(0).args[0].view).toEqual('files');
+				expect(handler.getCall(0).args[0].dir).toEqual('/somedir');
+			});
+			it('sends "show" event to current app and sets navigation', function() {
+				var handlerFiles = sinon.stub();
+				var handlerOther = sinon.stub();
+				$('#app-content-files').on('show', handlerFiles);
+				$('#app-content-other').on('show', handlerOther);
+				App._onPopState({view: 'other', dir: '/somedir'});
+				expect(handlerFiles.notCalled).toEqual(true);
+				expect(handlerOther.calledOnce).toEqual(true);
+
+				handlerFiles.reset();
+				handlerOther.reset();
+
+				App._onPopState({view: 'files', dir: '/somedir'});
+				expect(handlerFiles.calledOnce).toEqual(true);
+				expect(handlerOther.notCalled).toEqual(true);
+
+				expect(App.navigation.getActiveItem()).toEqual('files');
+				expect($('#app-content-files').hasClass('hidden')).toEqual(false);
+				expect($('#app-content-other').hasClass('hidden')).toEqual(true);
+			});
+			it('does not send "show" event to current app when already visible', function() {
+				var handler = sinon.stub();
+				$('#app-content-files').on('show', handler);
+				App._onPopState({view: 'files', dir: '/somedir'});
+				expect(handler.notCalled).toEqual(true);
+			});
+			it('state defaults to files app with root dir', function() {
+				var handler = sinon.stub();
+				parseUrlQueryStub.returns({});
+				$('#app-content-files').on('urlChanged', handler);
+				App._onPopState();
+				expect(handler.calledOnce).toEqual(true);
+				expect(handler.getCall(0).args[0].view).toEqual('files');
+				expect(handler.getCall(0).args[0].dir).toEqual('/');
+			});
+		});
+		describe('navigation', function() {
+			it('switches the navigation item and panel visibility when onpopstate', function() {
+				App._onPopState({view: 'other', dir: '/somedir'});
+				expect(App.navigation.getActiveItem()).toEqual('other');
+				expect($('#app-content-files').hasClass('hidden')).toEqual(true);
+				expect($('#app-content-other').hasClass('hidden')).toEqual(false);
+				expect($('li[data-id=files]').hasClass('selected')).toEqual(false);
+				expect($('li[data-id=other]').hasClass('selected')).toEqual(true);
+
+				App._onPopState({view: 'files', dir: '/somedir'});
+
+				expect(App.navigation.getActiveItem()).toEqual('files');
+				expect($('#app-content-files').hasClass('hidden')).toEqual(false);
+				expect($('#app-content-other').hasClass('hidden')).toEqual(true);
+				expect($('li[data-id=files]').hasClass('selected')).toEqual(true);
+				expect($('li[data-id=other]').hasClass('selected')).toEqual(false);
+			});
+			it('clicking on navigation switches the panel visibility', function() {
+				$('li[data-id=other]>a').click();
+				expect(App.navigation.getActiveItem()).toEqual('other');
+				expect($('#app-content-files').hasClass('hidden')).toEqual(true);
+				expect($('#app-content-other').hasClass('hidden')).toEqual(false);
+				expect($('li[data-id=files]').hasClass('selected')).toEqual(false);
+				expect($('li[data-id=other]').hasClass('selected')).toEqual(true);
+
+				$('li[data-id=files]>a').click();
+				expect(App.navigation.getActiveItem()).toEqual('files');
+				expect($('#app-content-files').hasClass('hidden')).toEqual(false);
+				expect($('#app-content-other').hasClass('hidden')).toEqual(true);
+				expect($('li[data-id=files]').hasClass('selected')).toEqual(true);
+				expect($('li[data-id=other]').hasClass('selected')).toEqual(false);
+			});
+			it('clicking on navigation sends "urlChanged" event', function() {
+				var handler = sinon.stub();
+				$('#app-content-other').on('urlChanged', handler);
+				$('li[data-id=other]>a').click();
+				expect(handler.calledOnce).toEqual(true);
+				expect(handler.getCall(0).args[0].view).toEqual('other');
+				expect(handler.getCall(0).args[0].dir).toEqual('/');
+			});
+		});
+	});
+});
diff --git a/apps/files/tests/js/breadcrumbSpec.js b/apps/files/tests/js/breadcrumbSpec.js
index 1bfe530..e3d9c75 100644
--- a/apps/files/tests/js/breadcrumbSpec.js
+++ b/apps/files/tests/js/breadcrumbSpec.js
@@ -20,7 +20,9 @@
 */
 
 /* global BreadCrumb */
-describe('BreadCrumb tests', function() {
+describe('OCA.Files.BreadCrumb tests', function() {
+	var BreadCrumb = OCA.Files.BreadCrumb;
+
 	describe('Rendering', function() {
 		var bc;
 		beforeEach(function() {
diff --git a/apps/files/tests/js/fileactionsSpec.js b/apps/files/tests/js/fileactionsSpec.js
index f5eafba..23a13be 100644
--- a/apps/files/tests/js/fileactionsSpec.js
+++ b/apps/files/tests/js/fileactionsSpec.js
@@ -19,20 +19,23 @@
 *
 */
 
-/* global OC, FileActions, FileList */
-describe('FileActions tests', function() {
-	var $filesTable;
+describe('OCA.Files.FileActions tests', function() {
+	var $filesTable, fileList;
+	var FileActions = OCA.Files.FileActions;
 
 	beforeEach(function() {
 		// init horrible parameters
-		var $body = $('body');
+		var $body = $('#testArea');
 		$body.append('<input type="hidden" id="dir" value="/subdir"></input>');
 		$body.append('<input type="hidden" id="permissions" value="31"></input>');
 		// dummy files table
 		$filesTable = $body.append('<table id="filestable"></table>');
-		FileList.files = [];
+		fileList = new OCA.Files.FileList($('#testArea'));
+		FileActions.registerDefaultActions(fileList);
 	});
 	afterEach(function() {
+		FileActions.clear();
+		fileList = undefined;
 		$('#dir, #permissions, #filestable').remove();
 	});
 	it('calling display() sets file actions', function() {
@@ -47,7 +50,7 @@ describe('FileActions tests', function() {
 		};
 
 		// note: FileActions.display() is called implicitly
-		var $tr = FileList.add(fileData);
+		var $tr = fileList.add(fileData);
 
 		// actions defined after call
 		expect($tr.find('.action.action-download').length).toEqual(1);
@@ -66,7 +69,7 @@ describe('FileActions tests', function() {
 			etag: 'a01234c',
 			mtime: '123456'
 		};
-		var $tr = FileList.add(fileData);
+		var $tr = fileList.add(fileData);
 
 		FileActions.display($tr.find('td.filename'), true);
 		FileActions.display($tr.find('td.filename'), true);
@@ -87,7 +90,7 @@ describe('FileActions tests', function() {
 			etag: 'a01234c',
 			mtime: '123456'
 		};
-		var $tr = FileList.add(fileData);
+		var $tr = fileList.add(fileData);
 		FileActions.display($tr.find('td.filename'), true);
 
 		$tr.find('.action-download').click();
@@ -97,7 +100,7 @@ describe('FileActions tests', function() {
 		redirectStub.restore();
 	});
 	it('deletes file when clicking delete', function() {
-		var deleteStub = sinon.stub(FileList, 'do_delete');
+		var deleteStub = sinon.stub(fileList, 'do_delete');
 		var fileData = {
 			id: 18,
 			type: 'file',
@@ -107,7 +110,7 @@ describe('FileActions tests', function() {
 			etag: 'a01234c',
 			mtime: '123456'
 		};
-		var $tr = FileList.add(fileData);
+		var $tr = fileList.add(fileData);
 		FileActions.display($tr.find('td.filename'), true);
 
 		$tr.find('.action.delete').click();
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index edb9e15..a0d8870 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -19,10 +19,9 @@
 *
 */
 
-/* global OC, FileList */
-describe('FileList tests', function() {
-	var testFiles, alertStub, notificationStub,
-		pushStateStub;
+describe('OCA.Files.FileList tests', function() {
+	var testFiles, alertStub, notificationStub, fileList;
+	var FileActions = OCA.Files.FileActions;
 
 	/**
 	 * Generate test file data
@@ -59,14 +58,12 @@ describe('FileList tests', function() {
 		// dummy files table
 		$body.append('<table id="filestable"></table>');
 
-		// prevents URL changes during tests
-		pushStateStub = sinon.stub(window.history, 'pushState');
-
 		alertStub = sinon.stub(OC.dialogs, 'alert');
 		notificationStub = sinon.stub(OC.Notification, 'show');
 
 		// init parameters and test table elements
 		$('#testArea').append(
+			'<div id="app-content-files">' +
 			'<input type="hidden" id="dir" value="/subdir"></input>' +
 			'<input type="hidden" id="permissions" value="31"></input>' +
 			// dummy controls
@@ -75,7 +72,7 @@ describe('FileList tests', function() {
 			'   <div class="notCreatable"></div>' +
 			'</div>' +
 			// dummy table
-			// TODO: at some point this will be rendered by the FileList class itself!
+			// TODO: at some point this will be rendered by the fileList class itself!
 			'<table id="filestable">' +
 			'<thead><tr>' +
 			'<th id="headerName" class="hidden column-name">' +
@@ -91,7 +88,8 @@ describe('FileList tests', function() {
 		   	'<tbody id="fileList"></tbody>' +
 			'<tfoot></tfoot>' +
 			'</table>' +
-			'<div id="emptycontent">Empty content message</div>'
+			'<div id="emptycontent">Empty content message</div>' +
+			'</div>'
 		);
 
 		testFiles = [{
@@ -124,27 +122,27 @@ describe('FileList tests', function() {
 			etag: '456'
 		}];
 
-		FileList.initialize();
+		fileList = new OCA.Files.FileList($('#app-content-files'));
+		FileActions.registerDefaultActions(fileList);
+		fileList.setFileActions(FileActions);
 	});
 	afterEach(function() {
 		testFiles = undefined;
-		FileList.initialized = false;
-		FileList.isEmpty = true;
-		delete FileList._reloadCall;
+		fileList = undefined;
 
+		FileActions.clear();
 		$('#dir, #permissions, #filestable').remove();
 		notificationStub.restore();
 		alertStub.restore();
-		pushStateStub.restore();
 	});
 	describe('Getters', function() {
 		it('Returns the current directory', function() {
 			$('#dir').val('/one/two/three');
-			expect(FileList.getCurrentDirectory()).toEqual('/one/two/three');
+			expect(fileList.getCurrentDirectory()).toEqual('/one/two/three');
 		});
 		it('Returns the directory permissions as int', function() {
 			$('#permissions').val('23');
-			expect(FileList.getDirectoryPermissions()).toEqual(23);
+			expect(fileList.getDirectoryPermissions()).toEqual(23);
 		});
 	});
 	describe('Adding files', function() {
@@ -167,7 +165,7 @@ describe('FileList tests', function() {
 				etag: 'a01234c',
 				mtime: '123456'
 			};
-			var $tr = FileList.add(fileData);
+			var $tr = fileList.add(fileData);
 
 			expect($tr).toBeDefined();
 			expect($tr[0].tagName.toLowerCase()).toEqual('tr');
@@ -182,7 +180,7 @@ describe('FileList tests', function() {
 			expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=testName.txt');
 
 			expect($tr.find('.filesize').text()).toEqual('1 kB');
-			expect(FileList.findFileEl('testName.txt')[0]).toEqual($tr[0]);
+			expect(fileList.findFileEl('testName.txt')[0]).toEqual($tr[0]);
 		});
 		it('generates dir element with correct attributes when calling add() with dir data', function() {
 			var fileData = {
@@ -194,7 +192,7 @@ describe('FileList tests', function() {
 				etag: 'a01234c',
 				mtime: '123456'
 			};
-			var $tr = FileList.add(fileData);
+			var $tr = fileList.add(fileData);
 
 			expect($tr).toBeDefined();
 			expect($tr[0].tagName.toLowerCase()).toEqual('tr');
@@ -209,7 +207,7 @@ describe('FileList tests', function() {
 
 			expect($tr.find('.filesize').text()).toEqual('1 kB');
 
-			expect(FileList.findFileEl('testFolder')[0]).toEqual($tr[0]);
+			expect(fileList.findFileEl('testFolder')[0]).toEqual($tr[0]);
 		});
 		it('generates file element with default attributes when calling add() with minimal data', function() {
 			var fileData = {
@@ -217,8 +215,8 @@ describe('FileList tests', function() {
 				name: 'testFile.txt'
 			};
 
-		    clock.tick(123456);
-			var $tr = FileList.add(fileData);
+			clock.tick(123456);
+			var $tr = fileList.add(fileData);
 
 			expect($tr).toBeDefined();
 			expect($tr[0].tagName.toLowerCase()).toEqual('tr');
@@ -238,8 +236,8 @@ describe('FileList tests', function() {
 				type: 'dir',
 				name: 'testFolder'
 			};
-		    clock.tick(123456);
-			var $tr = FileList.add(fileData);
+			clock.tick(123456);
+			var $tr = fileList.add(fileData);
 
 			expect($tr).toBeDefined();
 			expect($tr[0].tagName.toLowerCase()).toEqual('tr');
@@ -260,7 +258,7 @@ describe('FileList tests', function() {
 				name: 'testFolder',
 				size: '0'
 			};
-			var $tr = FileList.add(fileData);
+			var $tr = fileList.add(fileData);
 			expect($tr.find('.filesize').text()).toEqual('0 B');
 		});
 		it('adds new file to the end of the list', function() {
@@ -269,19 +267,19 @@ describe('FileList tests', function() {
 				type: 'file',
 				name: 'ZZZ.txt'
 			};
-			FileList.setFiles(testFiles);
-			$tr = FileList.add(fileData);
+			fileList.setFiles(testFiles);
+			$tr = fileList.add(fileData);
 			expect($tr.index()).toEqual(4);
 		});
 		it('inserts files in a sorted manner when insert option is enabled', function() {
 			var $tr;
 			for (var i = 0; i < testFiles.length; i++) {
-				FileList.add(testFiles[i]);
+				fileList.add(testFiles[i]);
 			}
-			expect(FileList.files[0].name).toEqual('somedir');
-			expect(FileList.files[1].name).toEqual('One.txt');
-			expect(FileList.files[2].name).toEqual('Three.pdf');
-			expect(FileList.files[3].name).toEqual('Two.jpg');
+			expect(fileList.files[0].name).toEqual('somedir');
+			expect(fileList.files[1].name).toEqual('One.txt');
+			expect(fileList.files[2].name).toEqual('Three.pdf');
+			expect(fileList.files[3].name).toEqual('Two.jpg');
 		});
 		it('inserts new file at correct position', function() {
 			var $tr;
@@ -290,12 +288,12 @@ describe('FileList tests', function() {
 				name: 'P comes after O.txt'
 			};
 			for (var i = 0; i < testFiles.length; i++) {
-				FileList.add(testFiles[i]);
+				fileList.add(testFiles[i]);
 			}
-			$tr = FileList.add(fileData);
+			$tr = fileList.add(fileData);
 			// after "One.txt"
 			expect($tr.index()).toEqual(2);
-			expect(FileList.files[2]).toEqual(fileData);
+			expect(fileList.files[2]).toEqual(fileData);
 		});
 		it('inserts new folder at correct position in insert mode', function() {
 			var $tr;
@@ -304,11 +302,11 @@ describe('FileList tests', function() {
 				name: 'somedir2 comes after somedir'
 			};
 			for (var i = 0; i < testFiles.length; i++) {
-				FileList.add(testFiles[i]);
+				fileList.add(testFiles[i]);
 			}
-			$tr = FileList.add(fileData);
+			$tr = fileList.add(fileData);
 			expect($tr.index()).toEqual(1);
-			expect(FileList.files[1]).toEqual(fileData);
+			expect(fileList.files[1]).toEqual(fileData);
 		});
 		it('inserts new file at the end correctly', function() {
 			var $tr;
@@ -317,21 +315,22 @@ describe('FileList tests', function() {
 				name: 'zzz.txt'
 			};
 			for (var i = 0; i < testFiles.length; i++) {
-				FileList.add(testFiles[i]);
+				fileList.add(testFiles[i]);
 			}
-			$tr = FileList.add(fileData);
+			$tr = fileList.add(fileData);
 			expect($tr.index()).toEqual(4);
-			expect(FileList.files[4]).toEqual(fileData);
+			expect(fileList.files[4]).toEqual(fileData);
 		});
 		it('removes empty content message and shows summary when adding first file', function() {
+			var $summary;
 			var fileData = {
 				type: 'file',
 				name: 'first file.txt',
 				size: 12
 			};
-			FileList.setFiles([]);
-			expect(FileList.isEmpty).toEqual(true);
-			FileList.add(fileData);
+			fileList.setFiles([]);
+			expect(fileList.isEmpty).toEqual(true);
+			fileList.add(fileData);
 			$summary = $('#filestable .summary');
 			expect($summary.hasClass('hidden')).toEqual(false);
 			// yes, ugly...
@@ -341,19 +340,20 @@ describe('FileList tests', function() {
 			expect($summary.find('.filesize').text()).toEqual('12 B');
 			expect($('#filestable thead th').hasClass('hidden')).toEqual(false);
 			expect($('#emptycontent').hasClass('hidden')).toEqual(true);
-			expect(FileList.isEmpty).toEqual(false);
+			expect(fileList.isEmpty).toEqual(false);
 		});
 	});
 	describe('Removing files from the list', function() {
 		it('Removes file from list when calling remove() and updates summary', function() {
+			var $summary;
 			var $removedEl;
-			FileList.setFiles(testFiles);
-			$removedEl = FileList.remove('One.txt');
+			fileList.setFiles(testFiles);
+			$removedEl = fileList.remove('One.txt');
 			expect($removedEl).toBeDefined();
 			expect($removedEl.attr('data-file')).toEqual('One.txt');
 			expect($('#fileList tr').length).toEqual(3);
-			expect(FileList.files.length).toEqual(3);
-			expect(FileList.findFileEl('One.txt').length).toEqual(0);
+			expect(fileList.files.length).toEqual(3);
+			expect(fileList.findFileEl('One.txt').length).toEqual(0);
 
 			$summary = $('#filestable .summary');
 			expect($summary.hasClass('hidden')).toEqual(false);
@@ -361,27 +361,28 @@ describe('FileList tests', function() {
 			expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(false);
 			expect($summary.find('.fileinfo').hasClass('hidden')).toEqual(false);
 			expect($summary.find('.filesize').text()).toEqual('69 kB');
-			expect(FileList.isEmpty).toEqual(false);
+			expect(fileList.isEmpty).toEqual(false);
 		});
 		it('Shows empty content when removing last file', function() {
-			FileList.setFiles([testFiles[0]]);
-			FileList.remove('One.txt');
+			var $summary;
+			fileList.setFiles([testFiles[0]]);
+			fileList.remove('One.txt');
 			expect($('#fileList tr').length).toEqual(0);
-			expect(FileList.files.length).toEqual(0);
-			expect(FileList.findFileEl('One.txt').length).toEqual(0);
+			expect(fileList.files.length).toEqual(0);
+			expect(fileList.findFileEl('One.txt').length).toEqual(0);
 
 			$summary = $('#filestable .summary');
 			expect($summary.hasClass('hidden')).toEqual(true);
 			expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
 			expect($('#emptycontent').hasClass('hidden')).toEqual(false);
-			expect(FileList.isEmpty).toEqual(true);
+			expect(fileList.isEmpty).toEqual(true);
 		});
 	});
 	describe('Deleting files', function() {
 		function doDelete() {
 			var request, query;
 			// note: normally called from FileActions
-			FileList.do_delete(['One.txt', 'Two.jpg']);
+			fileList.do_delete(['One.txt', 'Two.jpg']);
 
 			expect(fakeServer.requests.length).toEqual(1);
 			request = fakeServer.requests[0];
@@ -391,7 +392,8 @@ describe('FileList tests', function() {
 			expect(OC.parseQueryString(query)).toEqual({'dir': '/subdir', files: '["One.txt","Two.jpg"]'});
 		}
 		it('calls delete.php, removes the deleted entries and updates summary', function() {
-			FileList.setFiles(testFiles);
+			var $summary;
+			fileList.setFiles(testFiles);
 			doDelete();
 
 			fakeServer.requests[0].respond(
@@ -400,10 +402,10 @@ describe('FileList tests', function() {
 				JSON.stringify({status: 'success'})
 			);
 
-			expect(FileList.findFileEl('One.txt').length).toEqual(0);
-			expect(FileList.findFileEl('Two.jpg').length).toEqual(0);
-			expect(FileList.findFileEl('Three.pdf').length).toEqual(1);
-			expect(FileList.$fileList.find('tr').length).toEqual(2);
+			expect(fileList.findFileEl('One.txt').length).toEqual(0);
+			expect(fileList.findFileEl('Two.jpg').length).toEqual(0);
+			expect(fileList.findFileEl('Three.pdf').length).toEqual(1);
+			expect(fileList.$fileList.find('tr').length).toEqual(2);
 
 			$summary = $('#filestable .summary');
 			expect($summary.hasClass('hidden')).toEqual(false);
@@ -411,28 +413,29 @@ describe('FileList tests', function() {
 			expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(false);
 			expect($summary.find('.fileinfo').hasClass('hidden')).toEqual(false);
 			expect($summary.find('.filesize').text()).toEqual('57 kB');
-			expect(FileList.isEmpty).toEqual(false);
+			expect(fileList.isEmpty).toEqual(false);
 			expect($('#filestable thead th').hasClass('hidden')).toEqual(false);
 			expect($('#emptycontent').hasClass('hidden')).toEqual(true);
 
 			expect(notificationStub.notCalled).toEqual(true);
 		});
 		it('shows spinner on files to be deleted', function() {
-			FileList.setFiles(testFiles);
+			fileList.setFiles(testFiles);
 			doDelete();
 
-			expect(FileList.findFileEl('One.txt').find('.progress-icon:not(.delete-icon)').length).toEqual(1);
-			expect(FileList.findFileEl('Three.pdf').find('.delete-icon:not(.progress-icon)').length).toEqual(1);
+			expect(fileList.findFileEl('One.txt').find('.progress-icon:not(.delete-icon)').length).toEqual(1);
+			expect(fileList.findFileEl('Three.pdf').find('.delete-icon:not(.progress-icon)').length).toEqual(1);
 		});
 		it('shows spinner on all files when deleting all', function() {
-			FileList.setFiles(testFiles);
+			fileList.setFiles(testFiles);
 
-			FileList.do_delete();
+			fileList.do_delete();
 
-			expect(FileList.$fileList.find('tr .progress-icon:not(.delete-icon)').length).toEqual(4);
+			expect(fileList.$fileList.find('tr .progress-icon:not(.delete-icon)').length).toEqual(4);
 		});
 		it('updates summary when deleting last file', function() {
-			FileList.setFiles([testFiles[0], testFiles[1]]);
+			var $summary;
+			fileList.setFiles([testFiles[0], testFiles[1]]);
 			doDelete();
 
 			fakeServer.requests[0].respond(
@@ -441,17 +444,17 @@ describe('FileList tests', function() {
 				JSON.stringify({status: 'success'})
 			);
 
-			expect(FileList.$fileList.find('tr').length).toEqual(0);
+			expect(fileList.$fileList.find('tr').length).toEqual(0);
 
 			$summary = $('#filestable .summary');
 			expect($summary.hasClass('hidden')).toEqual(true);
-			expect(FileList.isEmpty).toEqual(true);
-			expect(FileList.files.length).toEqual(0);
+			expect(fileList.isEmpty).toEqual(true);
+			expect(fileList.files.length).toEqual(0);
 			expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
 			expect($('#emptycontent').hasClass('hidden')).toEqual(false);
 		});
 		it('bring back deleted item when delete call failed', function() {
-			FileList.setFiles(testFiles);
+			fileList.setFiles(testFiles);
 			doDelete();
 
 			fakeServer.requests[0].respond(
@@ -461,9 +464,9 @@ describe('FileList tests', function() {
 			);
 
 			// files are still in the list
-			expect(FileList.findFileEl('One.txt').length).toEqual(1);
-			expect(FileList.findFileEl('Two.jpg').length).toEqual(1);
-			expect(FileList.$fileList.find('tr').length).toEqual(4);
+			expect(fileList.findFileEl('One.txt').length).toEqual(1);
+			expect(fileList.findFileEl('Two.jpg').length).toEqual(1);
+			expect(fileList.$fileList.find('tr').length).toEqual(4);
 
 			expect(notificationStub.calledOnce).toEqual(true);
 		});
@@ -473,12 +476,12 @@ describe('FileList tests', function() {
 			var $input, request;
 
 			for (var i = 0; i < testFiles.length; i++) {
-				FileList.add(testFiles[i]);
+				fileList.add(testFiles[i]);
 			}
 
 			// trigger rename prompt
-			FileList.rename('One.txt');
-			$input = FileList.$fileList.find('input.filename');
+			fileList.rename('One.txt');
+			$input = fileList.$fileList.find('input.filename');
 			$input.val('Tu_after_three.txt').blur();
 
 			expect(fakeServer.requests.length).toEqual(1);
@@ -487,10 +490,10 @@ describe('FileList tests', function() {
 			expect(OC.parseQueryString(request.url)).toEqual({'dir': '/subdir', newname: 'Tu_after_three.txt', file: 'One.txt'});
 
 			// element is renamed before the request finishes
-			expect(FileList.findFileEl('One.txt').length).toEqual(0);
-			expect(FileList.findFileEl('Tu_after_three.txt').length).toEqual(1);
+			expect(fileList.findFileEl('One.txt').length).toEqual(0);
+			expect(fileList.findFileEl('Tu_after_three.txt').length).toEqual(1);
 			// input is gone
-			expect(FileList.$fileList.find('input.filename').length).toEqual(0);
+			expect(fileList.$fileList.find('input.filename').length).toEqual(0);
 		}
 		it('Inserts renamed file entry at correct position if rename ajax call suceeded', function() {
 			doRename();
@@ -504,9 +507,9 @@ describe('FileList tests', function() {
 			}));
 
 			// element stays renamed
-			expect(FileList.findFileEl('One.txt').length).toEqual(0);
-			expect(FileList.findFileEl('Tu_after_three.txt').length).toEqual(1);
-			expect(FileList.findFileEl('Tu_after_three.txt').index()).toEqual(2); // after Two.txt
+			expect(fileList.findFileEl('One.txt').length).toEqual(0);
+			expect(fileList.findFileEl('Tu_after_three.txt').length).toEqual(1);
+			expect(fileList.findFileEl('Tu_after_three.txt').index()).toEqual(2); // after Two.txt
 
 			expect(alertStub.notCalled).toEqual(true);
 		});
@@ -521,9 +524,9 @@ describe('FileList tests', function() {
 			}));
 
 			// element was reverted
-			expect(FileList.findFileEl('One.txt').length).toEqual(1);
-			expect(FileList.findFileEl('One.txt').index()).toEqual(1); // after somedir
-			expect(FileList.findFileEl('Tu_after_three.txt').length).toEqual(0);
+			expect(fileList.findFileEl('One.txt').length).toEqual(1);
+			expect(fileList.findFileEl('One.txt').index()).toEqual(1); // after somedir
+			expect(fileList.findFileEl('Tu_after_three.txt').length).toEqual(0);
 
 			expect(alertStub.calledOnce).toEqual(true);
 		});
@@ -538,7 +541,7 @@ describe('FileList tests', function() {
 				}
 			}));
 
-			$tr = FileList.findFileEl('Tu_after_three.txt');
+			$tr = fileList.findFileEl('Tu_after_three.txt');
 			expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=Tu_after_three.txt');
 		});
 		// FIXME: fix this in the source code!
@@ -555,17 +558,17 @@ describe('FileList tests', function() {
 				}
 			}));
 
-			$tr = FileList.findFileEl('Tu_after_three.txt');
+			$tr = fileList.findFileEl('Tu_after_three.txt');
 			expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=One.txt');
 		});
 	});
 	describe('Moving files', function() {
 		beforeEach(function() {
-			FileList.setFiles(testFiles);
+			fileList.setFiles(testFiles);
 		});
 		it('Moves single file to target folder', function() {
 			var request;
-			FileList.move('One.txt', '/somedir');
+			fileList.move('One.txt', '/somedir');
 
 			expect(fakeServer.requests.length).toEqual(1);
 			request = fakeServer.requests[0];
@@ -580,17 +583,17 @@ describe('FileList tests', function() {
 				}
 			}));
 
-			expect(FileList.findFileEl('One.txt').length).toEqual(0);
+			expect(fileList.findFileEl('One.txt').length).toEqual(0);
 
 			// folder size has increased
-			expect(FileList.findFileEl('somedir').data('size')).toEqual(262);
-			expect(FileList.findFileEl('somedir').find('.filesize').text()).toEqual('262 B');
+			expect(fileList.findFileEl('somedir').data('size')).toEqual(262);
+			expect(fileList.findFileEl('somedir').find('.filesize').text()).toEqual('262 B');
 
 			expect(notificationStub.notCalled).toEqual(true);
 		});
 		it('Moves list of files to target folder', function() {
 			var request;
-			FileList.move(['One.txt', 'Two.jpg'], '/somedir');
+			fileList.move(['One.txt', 'Two.jpg'], '/somedir');
 
 			expect(fakeServer.requests.length).toEqual(2);
 			request = fakeServer.requests[0];
@@ -609,11 +612,11 @@ describe('FileList tests', function() {
 				}
 			}));
 
-			expect(FileList.findFileEl('One.txt').length).toEqual(0);
+			expect(fileList.findFileEl('One.txt').length).toEqual(0);
 
 			// folder size has increased
-			expect(FileList.findFileEl('somedir').data('size')).toEqual(262);
-			expect(FileList.findFileEl('somedir').find('.filesize').text()).toEqual('262 B');
+			expect(fileList.findFileEl('somedir').data('size')).toEqual(262);
+			expect(fileList.findFileEl('somedir').find('.filesize').text()).toEqual('262 B');
 
 			fakeServer.requests[1].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
 				status: 'success',
@@ -623,17 +626,17 @@ describe('FileList tests', function() {
 				}
 			}));
 
-			expect(FileList.findFileEl('Two.jpg').length).toEqual(0);
+			expect(fileList.findFileEl('Two.jpg').length).toEqual(0);
 
 			// folder size has increased
-			expect(FileList.findFileEl('somedir').data('size')).toEqual(12311);
-			expect(FileList.findFileEl('somedir').find('.filesize').text()).toEqual('12 kB');
+			expect(fileList.findFileEl('somedir').data('size')).toEqual(12311);
+			expect(fileList.findFileEl('somedir').find('.filesize').text()).toEqual('12 kB');
 
 			expect(notificationStub.notCalled).toEqual(true);
 		});
 		it('Shows notification if a file could not be moved', function() {
 			var request;
-			FileList.move('One.txt', '/somedir');
+			fileList.move('One.txt', '/somedir');
 
 			expect(fakeServer.requests.length).toEqual(1);
 			request = fakeServer.requests[0];
@@ -647,7 +650,7 @@ describe('FileList tests', function() {
 				}
 			}));
 
-			expect(FileList.findFileEl('One.txt').length).toEqual(1);
+			expect(fileList.findFileEl('One.txt').length).toEqual(1);
 
 			expect(notificationStub.calledOnce).toEqual(true);
 			expect(notificationStub.getCall(0).args[0]).toEqual('Error while moving file');
@@ -655,47 +658,47 @@ describe('FileList tests', function() {
 	});
 	describe('List rendering', function() {
 		it('renders a list of files using add()', function() {
-			expect(FileList.files.length).toEqual(0);
-			expect(FileList.files).toEqual([]);
-			FileList.setFiles(testFiles);
+			expect(fileList.files.length).toEqual(0);
+			expect(fileList.files).toEqual([]);
+			fileList.setFiles(testFiles);
 			expect($('#fileList tr').length).toEqual(4);
-			expect(FileList.files.length).toEqual(4);
-			expect(FileList.files).toEqual(testFiles);
+			expect(fileList.files.length).toEqual(4);
+			expect(fileList.files).toEqual(testFiles);
 		});
 		it('updates summary using the file sizes', function() {
 			var $summary;
-			FileList.setFiles(testFiles);
+			fileList.setFiles(testFiles);
 			$summary = $('#filestable .summary');
 			expect($summary.hasClass('hidden')).toEqual(false);
 			expect($summary.find('.info').text()).toEqual('1 folder and 3 files');
 			expect($summary.find('.filesize').text()).toEqual('69 kB');
 		});
 		it('shows headers, summary and hide empty content message after setting files', function(){
-			FileList.setFiles(testFiles);
+			fileList.setFiles(testFiles);
 			expect($('#filestable thead th').hasClass('hidden')).toEqual(false);
 			expect($('#emptycontent').hasClass('hidden')).toEqual(true);
-			expect(FileList.$el.find('.summary').hasClass('hidden')).toEqual(false);
+			expect(fileList.$el.find('.summary').hasClass('hidden')).toEqual(false);
 		});
 		it('hides headers, summary and show empty content message after setting empty file list', function(){
-			FileList.setFiles([]);
+			fileList.setFiles([]);
 			expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
 			expect($('#emptycontent').hasClass('hidden')).toEqual(false);
-			expect(FileList.$el.find('.summary').hasClass('hidden')).toEqual(true);
+			expect(fileList.$el.find('.summary').hasClass('hidden')).toEqual(true);
 		});
 		it('hides headers, empty content message, and summary when list is empty and user has no creation permission', function(){
 			$('#permissions').val(0);
-			FileList.setFiles([]);
+			fileList.setFiles([]);
 			expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
 			expect($('#emptycontent').hasClass('hidden')).toEqual(true);
-			expect(FileList.$el.find('.summary').hasClass('hidden')).toEqual(true);
+			expect(fileList.$el.find('.summary').hasClass('hidden')).toEqual(true);
 		});
 		it('calling findFileEl() can find existing file element', function() {
-			FileList.setFiles(testFiles);
-			expect(FileList.findFileEl('Two.jpg').length).toEqual(1);
+			fileList.setFiles(testFiles);
+			expect(fileList.findFileEl('Two.jpg').length).toEqual(1);
 		});
 		it('calling findFileEl() returns empty when file not found in file', function() {
-			FileList.setFiles(testFiles);
-			expect(FileList.findFileEl('unexist.dat').length).toEqual(0);
+			fileList.setFiles(testFiles);
+			expect(fileList.findFileEl('unexist.dat').length).toEqual(0);
 		});
 		it('only add file if in same current directory', function() {
 			$('#dir').val('/current dir');
@@ -704,121 +707,122 @@ describe('FileList tests', function() {
 				name: 'testFile.txt',
 				directory: '/current dir'
 			};
-			var $tr = FileList.add(fileData);
-			expect(FileList.findFileEl('testFile.txt').length).toEqual(1);
+			var $tr = fileList.add(fileData);
+			expect(fileList.findFileEl('testFile.txt').length).toEqual(1);
 		});
 		it('triggers "fileActionsReady" event after update', function() {
 			var handler = sinon.stub();
-			FileList.$fileList.on('fileActionsReady', handler);
-			FileList.setFiles(testFiles);
+			fileList.$fileList.on('fileActionsReady', handler);
+			fileList.setFiles(testFiles);
 			expect(handler.calledOnce).toEqual(true);
 		});
 		it('triggers "updated" event after update', function() {
 			var handler = sinon.stub();
-			FileList.$fileList.on('updated', handler);
-			FileList.setFiles(testFiles);
+			fileList.$fileList.on('updated', handler);
+			fileList.setFiles(testFiles);
 			expect(handler.calledOnce).toEqual(true);
 		});
 		it('does not update summary when removing non-existing files', function() {
+			var $summary;
 			// single file
-			FileList.setFiles([testFiles[0]]);
+			fileList.setFiles([testFiles[0]]);
 			$summary = $('#filestable .summary');
 			expect($summary.hasClass('hidden')).toEqual(false);
 			expect($summary.find('.info').text()).toEqual('0 folders and 1 file');
-			FileList.remove('unexist.txt');
+			fileList.remove('unexist.txt');
 			expect($summary.hasClass('hidden')).toEqual(false);
 			expect($summary.find('.info').text()).toEqual('0 folders and 1 file');
 		});
 	});
 	describe('Rendering next page on scroll', function() {
 		beforeEach(function() {
-			FileList.setFiles(generateFiles(0, 64));
+			fileList.setFiles(generateFiles(0, 64));
 		});
 		it('renders only the first page', function() {
-			expect(FileList.files.length).toEqual(65);
+			expect(fileList.files.length).toEqual(65);
 			expect($('#fileList tr').length).toEqual(20);
 		});
 		it('renders the second page when scrolling down (trigger nextPage)', function() {
 			// TODO: can't simulate scrolling here, so calling nextPage directly
-			FileList._nextPage(true);
+			fileList._nextPage(true);
 			expect($('#fileList tr').length).toEqual(40);
-			FileList._nextPage(true);
+			fileList._nextPage(true);
 			expect($('#fileList tr').length).toEqual(60);
-			FileList._nextPage(true);
+			fileList._nextPage(true);
 			expect($('#fileList tr').length).toEqual(65);
-			FileList._nextPage(true);
+			fileList._nextPage(true);
 			// stays at 65
 			expect($('#fileList tr').length).toEqual(65);
 		});
 		it('inserts into the DOM if insertion point is in the visible page ', function() {
-			FileList.add({
+			fileList.add({
 				id: 2000,
 				type: 'file',
 				name: 'File with index 15b.txt'
 			});
 			expect($('#fileList tr').length).toEqual(21);
-			expect(FileList.findFileEl('File with index 15b.txt').index()).toEqual(16);
+			expect(fileList.findFileEl('File with index 15b.txt').index()).toEqual(16);
 		});
 		it('does not inserts into the DOM if insertion point is not the visible page ', function() {
-			FileList.add({
+			fileList.add({
 				id: 2000,
 				type: 'file',
 				name: 'File with index 28b.txt'
 			});
 			expect($('#fileList tr').length).toEqual(20);
-			expect(FileList.findFileEl('File with index 28b.txt').length).toEqual(0);
-			FileList._nextPage(true);
+			expect(fileList.findFileEl('File with index 28b.txt').length).toEqual(0);
+			fileList._nextPage(true);
 			expect($('#fileList tr').length).toEqual(40);
-			expect(FileList.findFileEl('File with index 28b.txt').index()).toEqual(29);
+			expect(fileList.findFileEl('File with index 28b.txt').index()).toEqual(29);
 		});
 		it('appends into the DOM when inserting a file after the last visible element', function() {
-			FileList.add({
+			fileList.add({
 				id: 2000,
 				type: 'file',
 				name: 'File with index 19b.txt'
 			});
 			expect($('#fileList tr').length).toEqual(21);
-			FileList._nextPage(true);
+			fileList._nextPage(true);
 			expect($('#fileList tr').length).toEqual(41);
 		});
 		it('appends into the DOM when inserting a file on the last page when visible', function() {
-			FileList._nextPage(true);
+			fileList._nextPage(true);
 			expect($('#fileList tr').length).toEqual(40);
-			FileList._nextPage(true);
+			fileList._nextPage(true);
 			expect($('#fileList tr').length).toEqual(60);
-			FileList._nextPage(true);
+			fileList._nextPage(true);
 			expect($('#fileList tr').length).toEqual(65);
-			FileList._nextPage(true);
-			FileList.add({
+			fileList._nextPage(true);
+			fileList.add({
 				id: 2000,
 				type: 'file',
 				name: 'File with index 88.txt'
 			});
 			expect($('#fileList tr').length).toEqual(66);
-			FileList._nextPage(true);
+			fileList._nextPage(true);
 			expect($('#fileList tr').length).toEqual(66);
 		});
 		it('shows additional page when appending a page of files and scrolling down', function() {
 			var newFiles = generateFiles(66, 81);
 			for (var i = 0; i < newFiles.length; i++) {
-				FileList.add(newFiles[i]);
+				fileList.add(newFiles[i]);
 			}
 			expect($('#fileList tr').length).toEqual(20);
-			FileList._nextPage(true);
+			fileList._nextPage(true);
 			expect($('#fileList tr').length).toEqual(40);
-			FileList._nextPage(true);
+			fileList._nextPage(true);
 			expect($('#fileList tr').length).toEqual(60);
-			FileList._nextPage(true);
+			fileList._nextPage(true);
 			expect($('#fileList tr').length).toEqual(80);
-			FileList._nextPage(true);
+			fileList._nextPage(true);
 			expect($('#fileList tr').length).toEqual(81);
-			FileList._nextPage(true);
+			fileList._nextPage(true);
 			expect($('#fileList tr').length).toEqual(81);
 		});
 		it('automatically renders next page when there are not enough elements visible', function() {
 			// delete the 15 first elements
 			for (var i = 0; i < 15; i++) {
-				FileList.remove(FileList.files[0].name);
+				fileList.remove(fileList.files[0].name);
 			}
 			// still makes sure that there are 20 elements visible, if any
 			expect($('#fileList tr').length).toEqual(25);
@@ -838,7 +842,7 @@ describe('FileList tests', function() {
 		}
 
 		beforeEach(function() {
-			previewLoadStub = sinon.stub(Files, 'lazyLoadPreview');
+			previewLoadStub = sinon.stub(OCA.Files.FileList.prototype, 'lazyLoadPreview');
 		});
 		afterEach(function() {
 			previewLoadStub.restore();
@@ -848,7 +852,7 @@ describe('FileList tests', function() {
 				type: 'file',
 				name: 'testFile.txt'
 			};
-			var $tr = FileList.add(fileData);
+			var $tr = fileList.add(fileData);
 			var $td = $tr.find('td.filename');
 			expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
 			expect(previewLoadStub.notCalled).toEqual(true);
@@ -858,7 +862,7 @@ describe('FileList tests', function() {
 				type: 'dir',
 				name: 'test dir'
 			};
-			var $tr = FileList.add(fileData);
+			var $tr = fileList.add(fileData);
 			var $td = $tr.find('td.filename');
 			expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/folder.svg');
 			expect(previewLoadStub.notCalled).toEqual(true);
@@ -869,7 +873,7 @@ describe('FileList tests', function() {
 				name: 'test dir',
 				icon: OC.webroot + '/core/img/filetypes/application-pdf.svg'
 			};
-			var $tr = FileList.add(fileData);
+			var $tr = fileList.add(fileData);
 			var $td = $tr.find('td.filename');
 			expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/application-pdf.svg');
 			expect(previewLoadStub.notCalled).toEqual(true);
@@ -880,12 +884,12 @@ describe('FileList tests', function() {
 				name: 'test dir',
 				isPreviewAvailable: true
 			};
-			var $tr = FileList.add(fileData);
+			var $tr = fileList.add(fileData);
 			var $td = $tr.find('td.filename');
 			expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
 			expect(previewLoadStub.calledOnce).toEqual(true);
 			// third argument is callback
-			previewLoadStub.getCall(0).args[2](OC.webroot + '/somepath.png');
+			previewLoadStub.getCall(0).args[0].callback(OC.webroot + '/somepath.png');
 			expect(getImageUrl($td)).toEqual(OC.webroot + '/somepath.png');
 		});
 		it('renders default file type icon when no icon was provided and no preview is available', function() {
@@ -894,7 +898,7 @@ describe('FileList tests', function() {
 				name: 'test dir',
 				isPreviewAvailable: false
 			};
-			var $tr = FileList.add(fileData);
+			var $tr = fileList.add(fileData);
 			var $td = $tr.find('td.filename');
 			expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
 			expect(previewLoadStub.notCalled).toEqual(true);
@@ -902,22 +906,22 @@ describe('FileList tests', function() {
 	});
 	describe('viewer mode', function() {
 		it('enabling viewer mode hides files table and action buttons', function() {
-			FileList.setViewerMode(true);
+			fileList.setViewerMode(true);
 			expect($('#filestable').hasClass('hidden')).toEqual(true);
 			expect($('.actions').hasClass('hidden')).toEqual(true);
 			expect($('.notCreatable').hasClass('hidden')).toEqual(true);
 		});
 		it('disabling viewer mode restores files table and action buttons', function() {
-			FileList.setViewerMode(true);
-			FileList.setViewerMode(false);
+			fileList.setViewerMode(true);
+			fileList.setViewerMode(false);
 			expect($('#filestable').hasClass('hidden')).toEqual(false);
 			expect($('.actions').hasClass('hidden')).toEqual(false);
 			expect($('.notCreatable').hasClass('hidden')).toEqual(true);
 		});
 		it('disabling viewer mode restores files table and action buttons with correct permissions', function() {
 			$('#permissions').val(0);
-			FileList.setViewerMode(true);
-			FileList.setViewerMode(false);
+			fileList.setViewerMode(true);
+			fileList.setViewerMode(false);
 			expect($('#filestable').hasClass('hidden')).toEqual(false);
 			expect($('.actions').hasClass('hidden')).toEqual(true);
 			expect($('.notCreatable').hasClass('hidden')).toEqual(false);
@@ -940,18 +944,18 @@ describe('FileList tests', function() {
 			]);
 		});
 		it('fetches file list from server and renders it when reload() is called', function() {
-			FileList.reload();
+			fileList.reload();
 			expect(fakeServer.requests.length).toEqual(1);
 			var url = fakeServer.requests[0].url;
 			var query = url.substr(url.indexOf('?') + 1);
 			expect(OC.parseQueryString(query)).toEqual({'dir': '/subdir', sort: 'name', sortdirection: 'asc'});
 			fakeServer.respond();
 			expect($('#fileList tr').length).toEqual(4);
-			expect(FileList.findFileEl('One.txt').length).toEqual(1);
+			expect(fileList.findFileEl('One.txt').length).toEqual(1);
 		});
 		it('switches dir and fetches file list when calling changeDirectory()', function() {
-			FileList.changeDirectory('/anothersubdir');
-			expect(FileList.getCurrentDirectory()).toEqual('/anothersubdir');
+			fileList.changeDirectory('/anothersubdir');
+			expect(fileList.getCurrentDirectory()).toEqual('/anothersubdir');
 			expect(fakeServer.requests.length).toEqual(1);
 			var url = fakeServer.requests[0].url;
 			var query = url.substr(url.indexOf('?') + 1);
@@ -965,14 +969,14 @@ describe('FileList tests', function() {
 					},
 					''
 			]);
-			FileList.changeDirectory('/unexist');
+			fileList.changeDirectory('/unexist');
 			fakeServer.respond();
-			expect(FileList.getCurrentDirectory()).toEqual('/');
+			expect(fileList.getCurrentDirectory()).toEqual('/');
 		});
 		it('shows mask before loading file list then hides it at the end', function() {
-			var showMaskStub = sinon.stub(FileList, 'showMask');
-			var hideMaskStub = sinon.stub(FileList, 'hideMask');
-			FileList.changeDirectory('/anothersubdir');
+			var showMaskStub = sinon.stub(fileList, 'showMask');
+			var hideMaskStub = sinon.stub(fileList, 'hideMask');
+			fileList.changeDirectory('/anothersubdir');
 			expect(showMaskStub.calledOnce).toEqual(true);
 			expect(hideMaskStub.calledOnce).toEqual(false);
 			fakeServer.respond();
@@ -981,18 +985,23 @@ describe('FileList tests', function() {
 			showMaskStub.restore();
 			hideMaskStub.restore();
 		});
-		it('changes URL to target dir', function() {
-			FileList.changeDirectory('/somedir');
-			expect(pushStateStub.calledOnce).toEqual(true);
-			expect(pushStateStub.getCall(0).args[0]).toEqual({dir: '/somedir'});
-			expect(pushStateStub.getCall(0).args[2]).toEqual(OC.webroot + '/index.php/apps/files?dir=/somedir');
+		it('triggers "changeDirectory" event when changing directory', function() {
+			var handler = sinon.stub();
+			$('#app-content-files').on('changeDirectory', handler);
+			fileList.changeDirectory('/somedir');
+			expect(handler.calledOnce).toEqual(true);
+			expect(handler.getCall(0).args[0].dir).toEqual('/somedir');
+		});
+		it('changes the directory when receiving "urlChanged" event', function() {
+			$('#app-content-files').trigger(new $.Event('urlChanged', {view: 'files', dir: '/somedir'}));
+			expect(fileList.getCurrentDirectory()).toEqual('/somedir');
 		});
 		it('refreshes breadcrumb after update', function() {
-			var setDirSpy = sinon.spy(FileList.breadcrumb, 'setDirectory');
-			FileList.changeDirectory('/anothersubdir');
+			var setDirSpy = sinon.spy(fileList.breadcrumb, 'setDirectory');
+			fileList.changeDirectory('/anothersubdir');
 			fakeServer.respond();
-			expect(FileList.breadcrumb.setDirectory.calledOnce).toEqual(true);
-			expect(FileList.breadcrumb.setDirectory.calledWith('/anothersubdir')).toEqual(true);
+			expect(fileList.breadcrumb.setDirectory.calledOnce).toEqual(true);
+			expect(fileList.breadcrumb.setDirectory.calledWith('/anothersubdir')).toEqual(true);
 			setDirSpy.restore();
 		});
 	});
@@ -1013,20 +1022,20 @@ describe('FileList tests', function() {
 			]);
 		});
 		it('clicking on root breadcrumb changes directory to root', function() {
-			FileList.changeDirectory('/subdir/two/three with space/four/five');
+			fileList.changeDirectory('/subdir/two/three with space/four/five');
 			fakeServer.respond();
-			var changeDirStub = sinon.stub(FileList, 'changeDirectory');
-			FileList.breadcrumb.$el.find('.crumb:eq(0)').click();
+			var changeDirStub = sinon.stub(fileList, 'changeDirectory');
+			fileList.breadcrumb.$el.find('.crumb:eq(0)').click();
 
 			expect(changeDirStub.calledOnce).toEqual(true);
 			expect(changeDirStub.getCall(0).args[0]).toEqual('/');
 			changeDirStub.restore();
 		});
 		it('clicking on breadcrumb changes directory', function() {
-			FileList.changeDirectory('/subdir/two/three with space/four/five');
+			fileList.changeDirectory('/subdir/two/three with space/four/five');
 			fakeServer.respond();
-			var changeDirStub = sinon.stub(FileList, 'changeDirectory');
-			FileList.breadcrumb.$el.find('.crumb:eq(3)').click();
+			var changeDirStub = sinon.stub(fileList, 'changeDirectory');
+			fileList.breadcrumb.$el.find('.crumb:eq(3)').click();
 
 			expect(changeDirStub.calledOnce).toEqual(true);
 			expect(changeDirStub.getCall(0).args[0]).toEqual('/subdir/two/three with space');
@@ -1034,9 +1043,9 @@ describe('FileList tests', function() {
 		});
 		it('dropping files on breadcrumb calls move operation', function() {
 			var request, query, testDir = '/subdir/two/three with space/four/five';
-			FileList.changeDirectory(testDir);
+			fileList.changeDirectory(testDir);
 			fakeServer.respond();
-			var $crumb = FileList.breadcrumb.$el.find('.crumb:eq(3)');
+			var $crumb = fileList.breadcrumb.$el.find('.crumb:eq(3)');
 			// no idea what this is but is required by the handler
 			var ui = {
 				helper: {
@@ -1049,7 +1058,7 @@ describe('FileList tests', function() {
 				$('<tr data-file="Two.jpg" data-dir="' + testDir + '"></tr>')
 			]);
 			// simulate drop event
-			FileList._onDropOnBreadCrumb(new $.Event('drop', {target: $crumb}), ui);
+			fileList._onDropOnBreadCrumb(new $.Event('drop', {target: $crumb}), ui);
 
 			// will trigger two calls to move.php (first one was previous list.php)
 			expect(fakeServer.requests.length).toEqual(3);
@@ -1075,10 +1084,10 @@ describe('FileList tests', function() {
 			});
 		});
 		it('dropping files on same dir breadcrumb does nothing', function() {
-			var request, query, testDir = '/subdir/two/three with space/four/five';
-			FileList.changeDirectory(testDir);
+			var testDir = '/subdir/two/three with space/four/five';
+			fileList.changeDirectory(testDir);
 			fakeServer.respond();
-			var $crumb = FileList.breadcrumb.$el.find('.crumb:last');
+			var $crumb = fileList.breadcrumb.$el.find('.crumb:last');
 			// no idea what this is but is required by the handler
 			var ui = {
 				helper: {
@@ -1091,7 +1100,7 @@ describe('FileList tests', function() {
 				$('<tr data-file="Two.jpg" data-dir="' + testDir + '"></tr>')
 			]);
 			// simulate drop event
-			FileList._onDropOnBreadCrumb(new $.Event('drop', {target: $crumb}), ui);
+			fileList._onDropOnBreadCrumb(new $.Event('drop', {target: $crumb}), ui);
 
 			// no extra server request
 			expect(fakeServer.requests.length).toEqual(1);
@@ -1099,32 +1108,32 @@ describe('FileList tests', function() {
 	});
 	describe('Download Url', function() {
 		it('returns correct download URL for single files', function() {
-			expect(Files.getDownloadUrl('some file.txt')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=some%20file.txt');
-			expect(Files.getDownloadUrl('some file.txt', '/anotherpath/abc')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fanotherpath%2Fabc&files=some%20file.txt');
+			expect(fileList.getDownloadUrl('some file.txt')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=some%20file.txt');
+			expect(fileList.getDownloadUrl('some file.txt', '/anotherpath/abc')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fanotherpath%2Fabc&files=some%20file.txt');
 			$('#dir').val('/');
-			expect(Files.getDownloadUrl('some file.txt')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=some%20file.txt');
+			expect(fileList.getDownloadUrl('some file.txt')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=some%20file.txt');
 		});
 		it('returns correct download URL for multiple files', function() {
-			expect(Files.getDownloadUrl(['a b c.txt', 'd e f.txt'])).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=%5B%22a%20b%20c.txt%22%2C%22d%20e%20f.txt%22%5D');
+			expect(fileList.getDownloadUrl(['a b c.txt', 'd e f.txt'])).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=%5B%22a%20b%20c.txt%22%2C%22d%20e%20f.txt%22%5D');
 		});
 		it('returns the correct ajax URL', function() {
-			expect(Files.getAjaxUrl('test', {a:1, b:'x y'})).toEqual(OC.webroot + '/index.php/apps/files/ajax/test.php?a=1&b=x%20y');
+			expect(fileList.getAjaxUrl('test', {a:1, b:'x y'})).toEqual(OC.webroot + '/index.php/apps/files/ajax/test.php?a=1&b=x%20y');
 		});
 	});
 	describe('File selection', function() {
 		beforeEach(function() {
-			FileList.setFiles(testFiles);
+			fileList.setFiles(testFiles);
 		});
 		it('Selects a file when clicking its checkbox', function() {
-			var $tr = FileList.findFileEl('One.txt');
+			var $tr = fileList.findFileEl('One.txt');
 			expect($tr.find('input:checkbox').prop('checked')).toEqual(false);
 			$tr.find('td.filename input:checkbox').click();
 
 			expect($tr.find('input:checkbox').prop('checked')).toEqual(true);
 		});
 		it('Selects/deselect a file when clicking on the name while holding Ctrl', function() {
-			var $tr = FileList.findFileEl('One.txt');
-			var $tr2 = FileList.findFileEl('Three.pdf');
+			var $tr = fileList.findFileEl('One.txt');
+			var $tr2 = fileList.findFileEl('Three.pdf');
 			var e;
 			expect($tr.find('input:checkbox').prop('checked')).toEqual(false);
 			expect($tr2.find('input:checkbox').prop('checked')).toEqual(false);
@@ -1142,7 +1151,7 @@ describe('FileList tests', function() {
 			expect($tr.find('input:checkbox').prop('checked')).toEqual(true);
 			expect($tr2.find('input:checkbox').prop('checked')).toEqual(true);
 
-			expect(_.pluck(FileList.getSelectedFiles(), 'name')).toEqual(['One.txt', 'Three.pdf']);
+			expect(_.pluck(fileList.getSelectedFiles(), 'name')).toEqual(['One.txt', 'Three.pdf']);
 
 			// deselect now
 			e = new $.Event('click');
@@ -1150,11 +1159,11 @@ describe('FileList tests', function() {
 			$tr2.find('td.filename .name').trigger(e);
 			expect($tr.find('input:checkbox').prop('checked')).toEqual(true);
 			expect($tr2.find('input:checkbox').prop('checked')).toEqual(false);
-			expect(_.pluck(FileList.getSelectedFiles(), 'name')).toEqual(['One.txt']);
+			expect(_.pluck(fileList.getSelectedFiles(), 'name')).toEqual(['One.txt']);
 		});
 		it('Selects a range when clicking on one file then Shift clicking on another one', function() {
-			var $tr = FileList.findFileEl('One.txt');
-			var $tr2 = FileList.findFileEl('Three.pdf');
+			var $tr = fileList.findFileEl('One.txt');
+			var $tr2 = fileList.findFileEl('Three.pdf');
 			var e;
 			$tr.find('td.filename input:checkbox').click();
 			e = new $.Event('click');
@@ -1163,16 +1172,16 @@ describe('FileList tests', function() {
 
 			expect($tr.find('input:checkbox').prop('checked')).toEqual(true);
 			expect($tr2.find('input:checkbox').prop('checked')).toEqual(true);
-			expect(FileList.findFileEl('Two.jpg').find('input:checkbox').prop('checked')).toEqual(true);
-			var selection = _.pluck(FileList.getSelectedFiles(), 'name');
+			expect(fileList.findFileEl('Two.jpg').find('input:checkbox').prop('checked')).toEqual(true);
+			var selection = _.pluck(fileList.getSelectedFiles(), 'name');
 			expect(selection.length).toEqual(3);
 			expect(selection).toContain('One.txt');
 			expect(selection).toContain('Two.jpg');
 			expect(selection).toContain('Three.pdf');
 		});
 		it('Selects a range when clicking on one file then Shift clicking on another one that is above the first one', function() {
-			var $tr = FileList.findFileEl('One.txt');
-			var $tr2 = FileList.findFileEl('Three.pdf');
+			var $tr = fileList.findFileEl('One.txt');
+			var $tr2 = fileList.findFileEl('Three.pdf');
 			var e;
 			$tr2.find('td.filename input:checkbox').click();
 			e = new $.Event('click');
@@ -1181,8 +1190,8 @@ describe('FileList tests', function() {
 
 			expect($tr.find('input:checkbox').prop('checked')).toEqual(true);
 			expect($tr2.find('input:checkbox').prop('checked')).toEqual(true);
-			expect(FileList.findFileEl('Two.jpg').find('input:checkbox').prop('checked')).toEqual(true);
-			var selection = _.pluck(FileList.getSelectedFiles(), 'name');
+			expect(fileList.findFileEl('Two.jpg').find('input:checkbox').prop('checked')).toEqual(true);
+			var selection = _.pluck(fileList.getSelectedFiles(), 'name');
 			expect(selection.length).toEqual(3);
 			expect(selection).toContain('One.txt');
 			expect(selection).toContain('Two.jpg');
@@ -1194,19 +1203,19 @@ describe('FileList tests', function() {
 			expect($('#select_all').prop('checked')).toEqual(true);
 		});
 		it('Selecting all files on the first visible page will not automatically check "select all" checkbox', function() {
-			FileList.setFiles(generateFiles(0, 41));
+			fileList.setFiles(generateFiles(0, 41));
 			expect($('#select_all').prop('checked')).toEqual(false);
 			$('#fileList tr td.filename input:checkbox').click();
 			expect($('#select_all').prop('checked')).toEqual(false);
 		});
 		it('Clicking "select all" will select/deselect all files', function() {
-			FileList.setFiles(generateFiles(0, 41));
+			fileList.setFiles(generateFiles(0, 41));
 			$('#select_all').click();
 			expect($('#select_all').prop('checked')).toEqual(true);
 			$('#fileList tr input:checkbox').each(function() {
 				expect($(this).prop('checked')).toEqual(true);
 			});
-			expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(42);
+			expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(42);
 
 			$('#select_all').click();
 			expect($('#select_all').prop('checked')).toEqual(false);
@@ -1214,71 +1223,71 @@ describe('FileList tests', function() {
 			$('#fileList tr input:checkbox').each(function() {
 				expect($(this).prop('checked')).toEqual(false);
 			});
-			expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(0);
+			expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(0);
 		});
 		it('Clicking "select all" then deselecting a file will uncheck "select all"', function() {
 			$('#select_all').click();
 			expect($('#select_all').prop('checked')).toEqual(true);
 
-			var $tr = FileList.findFileEl('One.txt');
+			var $tr = fileList.findFileEl('One.txt');
 			$tr.find('input:checkbox').click();
 
 			expect($('#select_all').prop('checked')).toEqual(false);
-			expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(3);
+			expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(3);
 		});
 		it('Updates the selection summary when doing a few manipulations with "Select all"', function() {
 			$('#select_all').click();
 			expect($('#select_all').prop('checked')).toEqual(true);
 
-			var $tr = FileList.findFileEl('One.txt');
+			var $tr = fileList.findFileEl('One.txt');
 			// unselect one
 			$tr.find('input:checkbox').click();
 
 			expect($('#select_all').prop('checked')).toEqual(false);
-			expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(3);
+			expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(3);
 
 			// select all
 			$('#select_all').click();
 			expect($('#select_all').prop('checked')).toEqual(true);
-			expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(4);
+			expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(4);
 
 			// unselect one
 			$tr.find('input:checkbox').click();
 			expect($('#select_all').prop('checked')).toEqual(false);
-			expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(3);
+			expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(3);
 
 			// re-select it
 			$tr.find('input:checkbox').click();
 			expect($('#select_all').prop('checked')).toEqual(true);
-			expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(4);
+			expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(4);
 		});
 		it('Auto-selects files on next page when "select all" is checked', function() {
-			FileList.setFiles(generateFiles(0, 41));
+			fileList.setFiles(generateFiles(0, 41));
 			$('#select_all').click();
 
-			expect(FileList.$fileList.find('tr input:checkbox:checked').length).toEqual(20);
-			FileList._nextPage(true);
-			expect(FileList.$fileList.find('tr input:checkbox:checked').length).toEqual(40);
-			FileList._nextPage(true);
-			expect(FileList.$fileList.find('tr input:checkbox:checked').length).toEqual(42);
-			expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(42);
+			expect(fileList.$fileList.find('tr input:checkbox:checked').length).toEqual(20);
+			fileList._nextPage(true);
+			expect(fileList.$fileList.find('tr input:checkbox:checked').length).toEqual(40);
+			fileList._nextPage(true);
+			expect(fileList.$fileList.find('tr input:checkbox:checked').length).toEqual(42);
+			expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(42);
 		});
 		it('Selecting files updates selection summary', function() {
 			var $summary = $('#headerName a.name>span:first');
 			expect($summary.text()).toEqual('Name');
-			FileList.findFileEl('One.txt').find('input:checkbox').click();
-			FileList.findFileEl('Three.pdf').find('input:checkbox').click();
-			FileList.findFileEl('somedir').find('input:checkbox').click();
+			fileList.findFileEl('One.txt').find('input:checkbox').click();
+			fileList.findFileEl('Three.pdf').find('input:checkbox').click();
+			fileList.findFileEl('somedir').find('input:checkbox').click();
 			expect($summary.text()).toEqual('1 folder & 2 files');
 		});
 		it('Unselecting files hides selection summary', function() {
 			var $summary = $('#headerName a.name>span:first');
-			FileList.findFileEl('One.txt').find('input:checkbox').click().click();
+			fileList.findFileEl('One.txt').find('input:checkbox').click().click();
 			expect($summary.text()).toEqual('Name');
 		});
 		it('Select/deselect files shows/hides file actions', function() {
 			var $actions = $('#headerName .selectedActions');
-			var $checkbox = FileList.findFileEl('One.txt').find('input:checkbox');
+			var $checkbox = fileList.findFileEl('One.txt').find('input:checkbox');
 			expect($actions.hasClass('hidden')).toEqual(true);
 			$checkbox.click();
 			expect($actions.hasClass('hidden')).toEqual(false);
@@ -1300,31 +1309,31 @@ describe('FileList tests', function() {
 					},
 					JSON.stringify(data)
 			]);
-			FileList.changeDirectory('/');
+			fileList.changeDirectory('/');
 			fakeServer.respond();
 			expect($('#select_all').prop('checked')).toEqual(false);
-			expect(_.pluck(FileList.getSelectedFiles(), 'name')).toEqual([]);
+			expect(_.pluck(fileList.getSelectedFiles(), 'name')).toEqual([]);
 		});
 		it('getSelectedFiles returns the selected files even when they are on the next page', function() {
 			var selectedFiles;
-			FileList.setFiles(generateFiles(0, 41));
+			fileList.setFiles(generateFiles(0, 41));
 			$('#select_all').click();
 			// unselect one to not have the "allFiles" case
-			FileList.$fileList.find('tr input:checkbox:first').click();
+			fileList.$fileList.find('tr input:checkbox:first').click();
 
 			// only 20 files visible, must still return all the selected ones
-			selectedFiles = _.pluck(FileList.getSelectedFiles(), 'name');
+			selectedFiles = _.pluck(fileList.getSelectedFiles(), 'name');
 
 			expect(selectedFiles.length).toEqual(41);
 		});
 		describe('Actions', function() {
 			beforeEach(function() {
-				FileList.findFileEl('One.txt').find('input:checkbox').click();
-				FileList.findFileEl('Three.pdf').find('input:checkbox').click();
-				FileList.findFileEl('somedir').find('input:checkbox').click();
+				fileList.findFileEl('One.txt').find('input:checkbox').click();
+				fileList.findFileEl('Three.pdf').find('input:checkbox').click();
+				fileList.findFileEl('somedir').find('input:checkbox').click();
 			});
 			it('getSelectedFiles returns the selected file data', function() {
-				var files = FileList.getSelectedFiles();
+				var files = fileList.getSelectedFiles();
 				expect(files.length).toEqual(3);
 				expect(files[0]).toEqual({
 					id: 1,
@@ -1352,8 +1361,8 @@ describe('FileList tests', function() {
 				});
 			});
 			it('Removing a file removes it from the selection', function() {
-				FileList.remove('Three.pdf');
-				var files = FileList.getSelectedFiles();
+				fileList.remove('Three.pdf');
+				var files = fileList.getSelectedFiles();
 				expect(files.length).toEqual(2);
 				expect(files[0]).toEqual({
 					id: 1,
@@ -1412,10 +1421,10 @@ describe('FileList tests', function() {
 						{ 'Content-Type': 'application/json' },
 						JSON.stringify({status: 'success'})
 					);
-					expect(FileList.findFileEl('One.txt').length).toEqual(0);
-					expect(FileList.findFileEl('Three.pdf').length).toEqual(0);
-					expect(FileList.findFileEl('somedir').length).toEqual(0);
-					expect(FileList.findFileEl('Two.jpg').length).toEqual(1);
+					expect(fileList.findFileEl('One.txt').length).toEqual(0);
+					expect(fileList.findFileEl('Three.pdf').length).toEqual(0);
+					expect(fileList.findFileEl('somedir').length).toEqual(0);
+					expect(fileList.findFileEl('Two.jpg').length).toEqual(1);
 				});
 				it('Deletes all files when all selected when "Delete" clicked', function() {
 					var request;
@@ -1431,20 +1440,20 @@ describe('FileList tests', function() {
 						{ 'Content-Type': 'application/json' },
 						JSON.stringify({status: 'success'})
 					);
-					expect(FileList.isEmpty).toEqual(true);
+					expect(fileList.isEmpty).toEqual(true);
 				});
 			});
 		});
 		it('resets the file selection on reload', function() {
-			FileList.$el.find('#select_all').click();
-			FileList.reload();
-			expect(FileList.$el.find('#select_all').prop('checked')).toEqual(false);
-			expect(FileList.getSelectedFiles()).toEqual([]);
+			fileList.$el.find('#select_all').click();
+			fileList.reload();
+			expect(fileList.$el.find('#select_all').prop('checked')).toEqual(false);
+			expect(fileList.getSelectedFiles()).toEqual([]);
 		});
 	});
 	describe('Sorting files', function() {
 		it('Sorts by name by default', function() {
-			FileList.reload();
+			fileList.reload();
 			expect(fakeServer.requests.length).toEqual(1);
 			var url = fakeServer.requests[0].url;
 			var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1));
@@ -1452,7 +1461,7 @@ describe('FileList tests', function() {
 			expect(query.sortdirection).toEqual('asc');
 		});
 		it('Reloads file list with a different sort when clicking on column header of unsorted column', function() {
-			FileList.$el.find('.column-size .columntitle').click();
+			fileList.$el.find('.column-size .columntitle').click();
 			expect(fakeServer.requests.length).toEqual(1);
 			var url = fakeServer.requests[0].url;
 			var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1));
@@ -1460,7 +1469,7 @@ describe('FileList tests', function() {
 			expect(query.sortdirection).toEqual('asc');
 		});
 		it('Toggles sort direction when clicking on already sorted column', function() {
-			FileList.$el.find('.column-name .columntitle').click();
+			fileList.$el.find('.column-name .columntitle').click();
 			expect(fakeServer.requests.length).toEqual(1);
 			var url = fakeServer.requests[0].url;
 			var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1));
@@ -1468,42 +1477,42 @@ describe('FileList tests', function() {
 			expect(query.sortdirection).toEqual('desc');
 		});
 		it('Toggles the sort indicator when clicking on a column header', function() {
-			var ASC_CLASS = FileList.SORT_INDICATOR_ASC_CLASS;
-			var DESC_CLASS = FileList.SORT_INDICATOR_DESC_CLASS;
-			FileList.$el.find('.column-size .columntitle').click();
+			var ASC_CLASS = fileList.SORT_INDICATOR_ASC_CLASS;
+			var DESC_CLASS = fileList.SORT_INDICATOR_DESC_CLASS;
+			fileList.$el.find('.column-size .columntitle').click();
 			// moves triangle to size column
 			expect(
-				FileList.$el.find('.column-name .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS)
+				fileList.$el.find('.column-name .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS)
 			).toEqual(false);
 			expect(
-				FileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS)
+				fileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS)
 			).toEqual(true);
 
 			// click again on size column, reverses direction
-			FileList.$el.find('.column-size .columntitle').click();
+			fileList.$el.find('.column-size .columntitle').click();
 			expect(
-				FileList.$el.find('.column-size .sort-indicator').hasClass(DESC_CLASS)
+				fileList.$el.find('.column-size .sort-indicator').hasClass(DESC_CLASS)
 			).toEqual(true);
 
 			// click again on size column, reverses direction
-			FileList.$el.find('.column-size .columntitle').click();
+			fileList.$el.find('.column-size .columntitle').click();
 			expect(
-				FileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS)
+				fileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS)
 			).toEqual(true);
 
 			// click on mtime column, moves indicator there
-			FileList.$el.find('.column-mtime .columntitle').click();
+			fileList.$el.find('.column-mtime .columntitle').click();
 			expect(
-				FileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS)
+				fileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS)
 			).toEqual(false);
 			expect(
-				FileList.$el.find('.column-mtime .sort-indicator').hasClass(ASC_CLASS)
+				fileList.$el.find('.column-mtime .sort-indicator').hasClass(ASC_CLASS)
 			).toEqual(true);
 		});
 		it('Uses correct sort comparator when inserting files', function() {
-			testFiles.sort(FileList.Comparators.size);
+			testFiles.sort(OCA.Files.FileList.Comparators.size);
 			// this will make it reload the testFiles with the correct sorting
-			FileList.$el.find('.column-size .columntitle').click();
+			fileList.$el.find('.column-size .columntitle').click();
 			expect(fakeServer.requests.length).toEqual(1);
 			fakeServer.requests[0].respond(
 				200,
@@ -1524,20 +1533,20 @@ describe('FileList tests', function() {
 				size: 40001,
 				etag: '999'
 			};
-			FileList.add(newFileData);
-			expect(FileList.files.length).toEqual(5);
-			expect(FileList.$fileList.find('tr').length).toEqual(5);
-			expect(FileList.findFileEl('One.txt').index()).toEqual(0);
-			expect(FileList.findFileEl('somedir').index()).toEqual(1);
-			expect(FileList.findFileEl('Two.jpg').index()).toEqual(2);
-			expect(FileList.findFileEl('new file.txt').index()).toEqual(3);
-			expect(FileList.findFileEl('Three.pdf').index()).toEqual(4);
+			fileList.add(newFileData);
+			expect(fileList.files.length).toEqual(5);
+			expect(fileList.$fileList.find('tr').length).toEqual(5);
+			expect(fileList.findFileEl('One.txt').index()).toEqual(0);
+			expect(fileList.findFileEl('somedir').index()).toEqual(1);
+			expect(fileList.findFileEl('Two.jpg').index()).toEqual(2);
+			expect(fileList.findFileEl('new file.txt').index()).toEqual(3);
+			expect(fileList.findFileEl('Three.pdf').index()).toEqual(4);
 		});
 		it('Uses correct reversed sort comparator when inserting files', function() {
-			testFiles.sort(FileList.Comparators.size);
+			testFiles.sort(OCA.Files.FileList.Comparators.size);
 			testFiles.reverse();
 			// this will make it reload the testFiles with the correct sorting
-			FileList.$el.find('.column-size .columntitle').click();
+			fileList.$el.find('.column-size .columntitle').click();
 			expect(fakeServer.requests.length).toEqual(1);
 			fakeServer.requests[0].respond(
 				200,
@@ -1551,7 +1560,7 @@ describe('FileList tests', function() {
 				})
 			);
 			// reverse sort
-			FileList.$el.find('.column-size .columntitle').click();
+			fileList.$el.find('.column-size .columntitle').click();
 			fakeServer.requests[1].respond(
 				200,
 				{ 'Content-Type': 'application/json' },
@@ -1571,14 +1580,14 @@ describe('FileList tests', function() {
 				size: 40001,
 				etag: '999'
 			};
-			FileList.add(newFileData);
-			expect(FileList.files.length).toEqual(5);
-			expect(FileList.$fileList.find('tr').length).toEqual(5);
-			expect(FileList.findFileEl('One.txt').index()).toEqual(4);
-			expect(FileList.findFileEl('somedir').index()).toEqual(3);
-			expect(FileList.findFileEl('Two.jpg').index()).toEqual(2);
-			expect(FileList.findFileEl('new file.txt').index()).toEqual(1);
-			expect(FileList.findFileEl('Three.pdf').index()).toEqual(0);
+			fileList.add(newFileData);
+			expect(fileList.files.length).toEqual(5);
+			expect(fileList.$fileList.find('tr').length).toEqual(5);
+			expect(fileList.findFileEl('One.txt').index()).toEqual(4);
+			expect(fileList.findFileEl('somedir').index()).toEqual(3);
+			expect(fileList.findFileEl('Two.jpg').index()).toEqual(2);
+			expect(fileList.findFileEl('new file.txt').index()).toEqual(1);
+			expect(fileList.findFileEl('Three.pdf').index()).toEqual(0);
 		});
 	});
 });
diff --git a/apps/files/tests/js/filesSpec.js b/apps/files/tests/js/filesSpec.js
index 7f88486..4f8d5a2 100644
--- a/apps/files/tests/js/filesSpec.js
+++ b/apps/files/tests/js/filesSpec.js
@@ -19,8 +19,9 @@
 *
 */
 
-/* global OC, Files */
-describe('Files tests', function() {
+describe('OCA.Files.Files tests', function() {
+	var Files = OCA.Files.Files;
+
 	describe('File name validation', function() {
 		it('Validates correct file names', function() {
 			var fileNames = [
@@ -83,18 +84,6 @@ describe('Files tests', function() {
 		});
 	});
 	describe('getDownloadUrl', function() {
-		var curDirStub;
-		beforeEach(function() {
-			curDirStub = sinon.stub(FileList, 'getCurrentDirectory');
-		});
-		afterEach(function() {
-			curDirStub.restore();
-		});
-		it('returns the ajax download URL when only filename specified', function() {
-			curDirStub.returns('/subdir');
-			var url = Files.getDownloadUrl('test file.txt');
-			expect(url).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=test%20file.txt');
-		});
 		it('returns the ajax download URL when filename and dir specified', function() {
 			var url = Files.getDownloadUrl('test file.txt', '/subdir');
 			expect(url).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=test%20file.txt');
diff --git a/apps/files/tests/js/filesummarySpec.js b/apps/files/tests/js/filesummarySpec.js
index c493700..5e39dd1 100644
--- a/apps/files/tests/js/filesummarySpec.js
+++ b/apps/files/tests/js/filesummarySpec.js
@@ -20,7 +20,8 @@
 */
 
 /* global FileSummary */
-describe('FileSummary tests', function() {
+describe('OCA.Files.FileSummary tests', function() {
+	var FileSummary = OCA.Files.FileSummary;
 	var $container;
 
 	beforeEach(function() {
diff --git a/apps/files_sharing/css/public.css b/apps/files_sharing/css/public.css
index 67d8470..8abeb8b 100644
--- a/apps/files_sharing/css/public.css
+++ b/apps/files_sharing/css/public.css
@@ -2,6 +2,10 @@
 	left: 0;
 }
 
+#filestable {
+	margin-top: 90px;
+}
+
 #preview {
 	background: #fff;
 	text-align: center;
diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js
index 0b4dec8..6ee5496 100644
--- a/apps/files_sharing/js/public.js
+++ b/apps/files_sharing/js/public.js
@@ -9,94 +9,141 @@
  */
 
 /* global OC, FileActions, FileList, Files */
+OCA.Sharing = {};
+if (!OCA.Files) {
+	OCA.Files = {};
+}
+OCA.Sharing.PublicApp = {
+	_initialized: false,
 
-$(document).ready(function() {
+	initialize: function($el) {
+		if (this._initialized) {
+			return;
+		}
+		this._initialized = true;
+		// file list mode ?
+		if ($el.find('#filestable')) {
+			this.fileList = new OCA.Files.FileList($el);
+		}
 
-	var mimetype = $('#mimetype').val();
+		var mimetype = $('#mimetype').val();
 
-	if (typeof FileActions !== 'undefined') {
-		// Show file preview if previewer is available, images are already handled by the template
-		if (mimetype.substr(0, mimetype.indexOf('/')) !== 'image' && $('.publicpreview').length === 0) {
-			// Trigger default action if not download TODO
-			var action = FileActions.getDefault(mimetype, 'file', OC.PERMISSION_READ);
-			if (typeof action !== 'undefined') {
-				action($('#filename').val());
+		if (typeof FileActions !== 'undefined') {
+			// Show file preview if previewer is available, images are already handled by the template
+			if (mimetype.substr(0, mimetype.indexOf('/')) !== 'image' && $('.publicpreview').length === 0) {
+				// Trigger default action if not download TODO
+				var action = FileActions.getDefault(mimetype, 'file', OC.PERMISSION_READ);
+				if (typeof action !== 'undefined') {
+					action($('#filename').val());
+				}
 			}
 		}
-	}
 
-	// dynamically load image previews
-	if (mimetype.substr(0, mimetype.indexOf('/')) === 'image' ) {
+		// dynamically load image previews
+		if (mimetype.substr(0, mimetype.indexOf('/')) === 'image' ) {
 
-		var params = {
-			x: $(document).width() * window.devicePixelRatio,
-			a: 'true',
-			file: encodeURIComponent($('#dir').val() + $('#filename').val()),
-			t: $('#sharingToken').val()
-		};
+			var params = {
+				x: $(document).width() * window.devicePixelRatio,
+				a: 'true',
+				file: encodeURIComponent($('#dir').val() + $('#filename').val()),
+				t: $('#sharingToken').val()
+			};
 
-		var img = $('<img class="publicpreview">');
-		img.attr('src', OC.filePath('files_sharing', 'ajax', 'publicpreview.php') + '?' + OC.buildQueryString(params));
-		img.appendTo('#imgframe');
-	}
+			var img = $('<img class="publicpreview">');
+			img.attr('src', OC.filePath('files_sharing', 'ajax', 'publicpreview.php') + '?' + OC.buildQueryString(params));
+			img.appendTo('#imgframe');
+		}
 
-	// override since the format is different
-	if (typeof Files !== 'undefined') {
-		Files.getDownloadUrl = function(filename, dir) {
-			if ($.isArray(filename)) {
-				filename = JSON.stringify(filename);
-			}
-			var path = dir || FileList.getCurrentDirectory();
-			var params = {
-				service: 'files',
-				t: $('#sharingToken').val(),
-				path: path,
-				files: filename,
-				download: null
+		if (this.fileList) {
+			// TODO: move this to a separate PublicFileList class that extends OCA.Files.FileList (+ unit tests)
+			this.fileList.getDownloadUrl = function(filename, dir) {
+				if ($.isArray(filename)) {
+					filename = JSON.stringify(filename);
+				}
+				var path = dir || FileList.getCurrentDirectory();
+				var params = {
+					service: 'files',
+					t: $('#sharingToken').val(),
+					path: path,
+					files: filename,
+					download: null
+				};
+				return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params);
 			};
-			return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params);
-		};
 
-		Files.getAjaxUrl = function(action, params) {
-			params = params || {};
-			params.t = $('#sharingToken').val();
-			return OC.filePath('files_sharing', 'ajax', action + '.php') + '?' + OC.buildQueryString(params);
-		};
+			this.fileList.getAjaxUrl = function(action, params) {
+				params = params || {};
+				params.t = $('#sharingToken').val();
+				return OC.filePath('files_sharing', 'ajax', action + '.php') + '?' + OC.buildQueryString(params);
+			};
 
-		FileList.linkTo = function(dir) {
-			var params = {
-				service: 'files',
-				t: $('#sharingToken').val(),
-				dir: dir
+			this.fileList.linkTo = function(dir) {
+				var params = {
+					service: 'files',
+					t: $('#sharingToken').val(),
+					dir: dir
+				};
+				return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params);
 			};
-			return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params);
-		};
-
-		Files.generatePreviewUrl = function(urlSpec) {
-			urlSpec.t = $('#dirToken').val();
-			return OC.generateUrl('/apps/files_sharing/ajax/publicpreview.php?') + $.param(urlSpec);
-		};
-
-		var file_upload_start = $('#file_upload_start');
-		file_upload_start.on('fileuploadadd', function(e, data) {
-			var fileDirectory = '';
-			if(typeof data.files[0].relativePath !== 'undefined') {
-				fileDirectory = data.files[0].relativePath;
-			}
 
-			// Add custom data to the upload handler
-			data.formData = {
-				requesttoken: $('#publicUploadRequestToken').val(),
-				dirToken: $('#dirToken').val(),
-				subdir: $('input#dir').val(),
-				file_directory: fileDirectory
+			this.fileList.generatePreviewUrl = function(urlSpec) {
+				urlSpec.t = $('#dirToken').val();
+				return OC.generateUrl('/apps/files_sharing/ajax/publicpreview.php?') + $.param(urlSpec);
 			};
+
+			var file_upload_start = $('#file_upload_start');
+			file_upload_start.on('fileuploadadd', function(e, data) {
+				var fileDirectory = '';
+				if(typeof data.files[0].relativePath !== 'undefined') {
+					fileDirectory = data.files[0].relativePath;
+				}
+
+				// Add custom data to the upload handler
+				data.formData = {
+					requesttoken: $('#publicUploadRequestToken').val(),
+					dirToken: $('#dirToken').val(),
+					subdir: $('input#dir').val(),
+					file_directory: fileDirectory
+				};
+			});
+
+			this.fileActions = _.extend({}, OCA.Files.FileActions);
+			this.fileActions.registerDefaultActions(this.fileList);
+			delete this.fileActions.actions.all.Share;
+			this.fileList.setFileActions(this.fileActions);
+
+			this.fileList.changeDirectory($('#dir').val() || '/', false, true);
+
+			// URL history handling
+			this.fileList.$el.on('changeDirectory', _.bind(this._onDirectoryChanged, this));
+			OC.Util.History.addOnPopStateHandler(_.bind(this._onUrlChanged, this));
+		}
+
+		$(document).on('click', '#directLink', function() {
+			$(this).focus();
+			$(this).select();
 		});
-	}
 
-	$(document).on('click', '#directLink', function() {
-		$(this).focus();
-		$(this).select();
-	});
+		// legacy
+		window.FileList = this.fileList;
+	},
 
+	_onDirectoryChanged: function(e) {
+		OC.Util.History.pushState({
+			service: 'files',
+			t: $('#sharingToken').val(),
+			// arghhhh, why is this not called "dir" !?
+			path: e.dir
+		});
+	},
+
+	_onUrlChanged: function(params) {
+		this.fileList.changeDirectory(params.path || params.dir, false, true);
+	}
+};
+
+$(document).ready(function() {
+	var App = OCA.Sharing.PublicApp;
+	App.initialize($('#preview'));
 });
+
diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js
index 3c7c923..ac46ab7 100644
--- a/apps/files_sharing/js/share.js
+++ b/apps/files_sharing/js/share.js
@@ -11,10 +11,9 @@
 /* global OC, t, FileList, FileActions */
 $(document).ready(function() {
 
-	var disableSharing = $('#disableSharing').data('status'),
-		sharesLoaded = false;
+	var sharesLoaded = false;
 
-	if (typeof OC.Share !== 'undefined' && typeof FileActions !== 'undefined'  && !disableSharing) {
+	if (typeof OC.Share !== 'undefined' && typeof FileActions !== 'undefined') {
 		var oldCreateRow = FileList._createRow;
 		FileList._createRow = function(fileData) {
 			var tr = oldCreateRow.apply(this, arguments);
diff --git a/apps/files_sharing/public.php b/apps/files_sharing/public.php
index e17ffc4..a73d97f 100644
--- a/apps/files_sharing/public.php
+++ b/apps/files_sharing/public.php
@@ -149,7 +149,7 @@ if (isset($path)) {
 
 			$freeSpace=OCP\Util::freeSpace($path);
 			$uploadLimit=OCP\Util::uploadLimit();
-			$folder = new OCP\Template('files', 'index', '');
+			$folder = new OCP\Template('files', 'list', '');
 			$folder->assign('dir', $getPath);
 			$folder->assign('dirToken', $linkItem['token']);
 			$folder->assign('permissions', OCP\PERMISSION_READ);
@@ -162,7 +162,6 @@ if (isset($path)) {
 			$folder->assign('uploadLimit', $uploadLimit); // PHP upload limit
 			$folder->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true)));
 			$folder->assign('usedSpacePercent', 0);
-			$folder->assign('disableSharing', true);
 			$folder->assign('trash', false);
 			$tmpl->assign('folder', $folder->fetchPage());
 			$allowZip = OCP\Config::getSystemValue('allowZipDownload', true);
diff --git a/apps/files_trashbin/appinfo/app.php b/apps/files_trashbin/appinfo/app.php
index 219c5d6..06c2e34 100644
--- a/apps/files_trashbin/appinfo/app.php
+++ b/apps/files_trashbin/appinfo/app.php
@@ -5,11 +5,11 @@ $l = OC_L10N::get('files_trashbin');
 \OCA\Files_Trashbin\Trashbin::registerHooks();
 
 \OCA\Files\App::getNavigationManager()->add(
-	array(
-		"id" => 'trashbin',
-		"appname" => 'files_trashbin',
-		"script" => 'index.php',
-		"order" => 1,
-		"name" => $l->t('Deleted files')
-	)
+array(
+	"id" => 'trashbin',
+	"appname" => 'files_trashbin',
+	"script" => 'index.php',
+	"order" => 1,
+	"name" => $l->t('Deleted files')
+)
 );
diff --git a/apps/files_trashbin/index.php b/apps/files_trashbin/index.php
index 4c55278..08227b7 100644
--- a/apps/files_trashbin/index.php
+++ b/apps/files_trashbin/index.php
@@ -5,12 +5,8 @@ OCP\User::checkLoggedIn();
 
 
 $tmpl = new OCP\Template('files_trashbin', 'index', '');
-// TODO: re-enable after making sure the scripts doesn't
-// override the files app
-/*
-OCP\Util::addScript('files_trashbin', 'disableDefaultActions');
 OCP\Util::addStyle('files_trashbin', 'trash');
+OCP\Util::addScript('files_trashbin', 'app');
+OCP\Util::addScript('files_trashbin', 'files');
 OCP\Util::addScript('files_trashbin', 'filelist');
-OCP\Util::addScript('files_trashbin', 'trash');
- */
 $tmpl->printPage();
diff --git a/apps/files_trashbin/js/app.js b/apps/files_trashbin/js/app.js
new file mode 100644
index 0000000..9ab78e7
--- /dev/null
+++ b/apps/files_trashbin/js/app.js
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2014
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+OCA.Trashbin = {};
+OCA.Trashbin.App = {
+	_initialized: false,
+
+	initialize: function($el) {
+		if (this._initialized) {
+			return;
+		}
+		this._initialized = true;
+		this.fileList = new OCA.Trashbin.FileList($el);
+		this.registerFileActions(this.fileList);
+	},
+
+	registerFileActions: function(fileList) {
+		var self = this;
+		var fileActions = _.extend({}, OCA.Files.FileActions);
+		fileActions.clear();
+		fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename) {
+			var dir = fileList.getCurrentDirectory();
+			if (dir !== '/') {
+				dir = dir + '/';
+			}
+			fileList.changeDirectory(dir + filename);
+		});
+
+		fileActions.setDefault('dir', 'Open');
+
+		fileActions.register('all', 'Restore', OC.PERMISSION_READ, OC.imagePath('core', 'actions/history'), function(filename) {
+			var tr = fileList.findFileEl(filename);
+			var deleteAction = tr.children("td.date").children(".action.delete");
+			deleteAction.removeClass('delete-icon').addClass('progress-icon');
+			fileList.disableActions();
+			$.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'), {
+					files: JSON.stringify([filename]),
+					dir: fileList.getCurrentDirectory()
+				},
+				_.bind(fileList._removeCallback, fileList)
+			);
+		}, t('files_trashbin', 'Restore'));
+
+		fileActions.register('all', 'Delete', OC.PERMISSION_READ, function() {
+			return OC.imagePath('core', 'actions/delete');
+		}, function(filename) {
+			$('.tipsy').remove();
+			var tr = fileList.findFileEl(filename);
+			var deleteAction = tr.children("td.date").children(".action.delete");
+			deleteAction.removeClass('delete-icon').addClass('progress-icon');
+			fileList.disableActions();
+			$.post(OC.filePath('files_trashbin', 'ajax', 'delete.php'), {
+					files: JSON.stringify([filename]),
+					dir: fileList.getCurrentDirectory()
+				},
+				_.bind(fileList._removeCallback, fileList)
+			);
+		});
+		fileList.setFileActions(fileActions);
+	}
+};
+
+$(document).ready(function() {
+	$('#app-content-trashbin').on('show', function() {
+		var App = OCA.Trashbin.App;
+		App.initialize($('#app-content-trashbin'));
+		// force breadcrumb init
+		// App.fileList.changeDirectory(App.fileList.getCurrentDirectory(), false, true);
+	});
+});
+
diff --git a/apps/files_trashbin/js/disableDefaultActions.js b/apps/files_trashbin/js/disableDefaultActions.js
deleted file mode 100644
index 50ceaf4..0000000
--- a/apps/files_trashbin/js/disableDefaultActions.js
+++ /dev/null
@@ -1,3 +0,0 @@
-/* disable download and sharing actions */
-var disableDownloadActions = true;
-var trashBinApp = true;
diff --git a/apps/files_trashbin/js/filelist.js b/apps/files_trashbin/js/filelist.js
index 00fc7e1..d320695 100644
--- a/apps/files_trashbin/js/filelist.js
+++ b/apps/files_trashbin/js/filelist.js
@@ -1,8 +1,14 @@
-/* global OC, t, FileList */
+/*
+ * Copyright (c) 2014
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
 (function() {
-	FileList.appName = t('files_trashbin', 'Deleted files');
-
-	FileList._deletedRegExp = new RegExp(/^(.+)\.d[0-9]+$/);
+	var DELETED_REGEXP = new RegExp(/^(.+)\.d[0-9]+$/);
 
 	/**
 	 * Convert a file name in the format filename.d12345 to the real file name.
@@ -11,195 +17,236 @@
 	 * @param name file name
 	 * @return converted file name
 	 */
-	FileList.getDeletedFileName = function(name) {
+	function getDeletedFileName(name) {
 		name = OC.basename(name);
-		var match = FileList._deletedRegExp.exec(name);
+		var match = DELETED_REGEXP.exec(name);
 		if (match && match.length > 1) {
 			name = match[1];
 		}
 		return name;
-	};
-
-	var oldSetCurrentDir = FileList._setCurrentDir;
-	FileList._setCurrentDir = function(targetDir) {
-		oldSetCurrentDir.apply(this, arguments);
+	}
 
-		var baseDir = OC.basename(targetDir);
-		if (baseDir !== '') {
-			FileList.setPageTitle(FileList.getDeletedFileName(baseDir));
-		}
+	var FileList = function($el) {
+		this.initialize($el);
 	};
+	FileList.prototype = _.extend({}, OCA.Files.FileList.prototype, {
+		appName: t('files_trashbin', 'Deleted files'),
 
-	var oldCreateRow = FileList._createRow;
-	FileList._createRow = function() {
-		// FIXME: MEGAHACK until we find a better solution
-		var tr = oldCreateRow.apply(this, arguments);
-		tr.find('td.filesize').remove();
-		return tr;
-	};
+		initialize: function() {
+			var result = OCA.Files.FileList.prototype.initialize.apply(this, arguments);
+			this.$el.find('.undelete').click('click', _.bind(this._onClickRestoreSelected, this));
 
-	FileList._onClickBreadCrumb = function(e) {
-		var $el = $(e.target).closest('.crumb'),
-			index = $el.index(),
-			$targetDir = $el.data('dir');
-		// first one is home, let the link makes it default action
-		if (index !== 0) {
-			e.preventDefault();
-			FileList.changeDirectory($targetDir);
-		}
-	};
+			this.setSort('mtime', 'desc');
 
-	var oldRenderRow = FileList._renderRow;
-	FileList._renderRow = function(fileData, options) {
-		options = options || {};
-		var dir = FileList.getCurrentDirectory();
-		var dirListing = dir !== '' && dir !== '/';
-		// show deleted time as mtime
-		if (fileData.mtime) {
-			fileData.mtime = parseInt(fileData.mtime, 10);
-		}
-		if (!dirListing) {
-			fileData.displayName = fileData.name;
-			fileData.name = fileData.name + '.d' + Math.floor(fileData.mtime / 1000);
-		}
-		return oldRenderRow.call(this, fileData, options);
-	};
+			// override crumb URL maker
+			this.breadcrumb.getCrumbUrl = function(part, index) {
+				return OC.linkTo('files_trashbin', 'index.php')+"?view=trashbin&dir=" + encodeURIComponent(part.dir);
+			};
+			/**
+			 * Override crumb making to add "Deleted Files" entry
+			 * and convert files with ".d" extensions to a more
+			 * user friendly name.
+			 */
+			this.breadcrumb._makeCrumbs = function() {
+				var parts = OCA.Files.BreadCrumb.prototype._makeCrumbs.apply(this, arguments);
+				for (var i = 1; i < parts.length; i++) {
+					parts[i].name = getDeletedFileName(parts[i].name);
+				}
+				return parts;
+			};
 
-	FileList.linkTo = function(dir){
-		return OC.linkTo('files_trashbin', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/');
-	};
+			return result;
+		},
 
-	FileList.updateEmptyContent = function(){
-		var $fileList = $('#fileList');
-		var exists = $fileList.find('tr:first').exists();
-		$('#emptycontent').toggleClass('hidden', exists);
-		$('#filestable th').toggleClass('hidden', !exists);
-	};
+		_setCurrentDir: function(targetDir) {
+			OCA.Files.FileList.prototype._setCurrentDir.apply(this, arguments);
 
-	var oldInit = FileList.initialize;
-	FileList.initialize = function() {
-		var result = oldInit.apply(this, arguments);
-		$('.undelete').click('click', FileList._onClickRestoreSelected);
-		this.setSort('mtime', 'desc');
-		return result;
-	};
-
-	FileList._removeCallback = function(result) {
-		if (result.status !== 'success') {
-			OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
-		}
+			var baseDir = OC.basename(targetDir);
+			if (baseDir !== '') {
+				this.setPageTitle(getDeletedFileName(baseDir));
+			}
+		},
+
+		_createRow: function() {
+			// FIXME: MEGAHACK until we find a better solution
+			var tr = OCA.Files.FileList.prototype._createRow.apply(this, arguments);
+			tr.find('td.filesize').remove();
+			return tr;
+		},
+
+		_renderRow: function(fileData, options) {
+			options = options || {};
+			var dir = this.getCurrentDirectory();
+			var dirListing = dir !== '' && dir !== '/';
+			// show deleted time as mtime
+			if (fileData.mtime) {
+				fileData.mtime = parseInt(fileData.mtime, 10);
+			}
+			if (!dirListing) {
+				fileData.displayName = fileData.name;
+				fileData.name = fileData.name + '.d' + Math.floor(fileData.mtime / 1000);
+			}
+			return OCA.Files.FileList.prototype._renderRow.call(this, fileData, options);
+		},
 
-		var files = result.data.success;
-		var $el;
-		for (var i = 0; i < files.length; i++) {
-			$el = FileList.remove(OC.basename(files[i].filename), {updateSummary: false});
-			FileList.fileSummary.remove({type: $el.attr('data-type'), size: $el.attr('data-size')});
-		}
-		FileList.fileSummary.update();
-		FileList.updateEmptyContent();
-		enableActions();
-	}
+		getAjaxUrl: function(action, params) {
+			var q = '';
+			if (params) {
+				q = '?' + OC.buildQueryString(params);
+			}
+			return OC.filePath('files_trashbin', 'ajax', action + '.php') + q;
+		},
+
+		linkTo: function(dir){
+			return OC.linkTo('files_trashbin', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/');
+		},
+
+		updateEmptyContent: function(){
+			var exists = this.$fileList.find('tr:first').exists();
+			this.$el.find('#emptycontent').toggleClass('hidden', exists);
+			this.$el.find('#filestable th').toggleClass('hidden', !exists);
+		},
+
+		_removeCallback: function(result) {
+			if (result.status !== 'success') {
+				OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
+			}
 
-	FileList._onClickRestoreSelected = function(event) {
-		event.preventDefault();
-		var allFiles = $('#select_all').is(':checked');
-		var files = [];
-		var params = {};
-		disableActions();
-		if (allFiles) {
-			FileList.showMask();
-			params = {
-				allfiles: true,
-				dir: FileList.getCurrentDirectory()
-			};
-		}
-		else {
-			files = _.pluck(FileList.getSelectedFiles(), 'name');
+			var files = result.data.success;
+			var $el;
 			for (var i = 0; i < files.length; i++) {
-				var deleteAction = FileList.findFileEl(files[i]).children("td.date").children(".action.delete");
-				deleteAction.removeClass('delete-icon').addClass('progress-icon');
+				$el = this.remove(OC.basename(files[i].filename), {updateSummary: false});
+				this.fileSummary.remove({type: $el.attr('data-type'), size: $el.attr('data-size')});
 			}
-			params = {
-				files: JSON.stringify(files),
-				dir: FileList.getCurrentDirectory()
-			};
-		}
+			this.fileSummary.update();
+			this.updateEmptyContent();
+			this.enableActions();
+		},
 
-		$.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'),
-			params,
-			function(result) {
-				if (allFiles) {
-					if (result.status !== 'success') {
-						OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
-					}
-					FileList.hideMask();
-					// simply remove all files
-					FileList.setFiles([]);
-					enableActions();
-				}
-				else {
-					FileList._removeCallback(result);
-				}
+		_onClickRestoreSelected: function(event) {
+			event.preventDefault();
+			var self = this;
+			var allFiles = this.$el.find('#select_all').is(':checked');
+			var files = [];
+			var params = {};
+			this.disableActions();
+			if (allFiles) {
+				this.showMask();
+				params = {
+					allfiles: true,
+					dir: this.getCurrentDirectory()
+				};
 			}
-		);
-	};
-
-	FileList._onClickDeleteSelected = function(event) {
-		event.preventDefault();
-		var allFiles = $('#select_all').is(':checked');
-		var files = [];
-		var params = {};
-		if (allFiles) {
-			params = {
-				allfiles: true,
-				dir: FileList.getCurrentDirectory()
-			};
-		}
-		else {
-			files = _.pluck(FileList.getSelectedFiles(), 'name');
-			params = {
-				files: JSON.stringify(files),
-				dir: FileList.getCurrentDirectory()
-			};
-		}
-
-		disableActions();
-		if (allFiles) {
-			FileList.showMask();
-		}
-		else {
-			for (var i = 0; i < files.length; i++) {
-				var deleteAction = FileList.findFileEl(files[i]).children("td.date").children(".action.delete");
-				deleteAction.removeClass('delete-icon').addClass('progress-icon');
+			else {
+				files = _.pluck(this.getSelectedFiles(), 'name');
+				for (var i = 0; i < files.length; i++) {
+					var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete");
+					deleteAction.removeClass('delete-icon').addClass('progress-icon');
+				}
+				params = {
+					files: JSON.stringify(files),
+					dir: this.getCurrentDirectory()
+				};
 			}
-		}
 
-		$.post(OC.filePath('files_trashbin', 'ajax', 'delete.php'),
+			$.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'),
 				params,
 				function(result) {
 					if (allFiles) {
 						if (result.status !== 'success') {
 							OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
 						}
-						FileList.hideMask();
+						self.hideMask();
 						// simply remove all files
-						FileList.setFiles([]);
-						enableActions();
+						self.setFiles([]);
+						self.enableActions();
 					}
 					else {
-						FileList._removeCallback(result);
+						self._removeCallback(result);
 					}
 				}
-		);
-	};
+			);
+		},
 
-	var oldClickFile = FileList._onClickFile;
-	FileList._onClickFile = function(event) {
-		var mime = $(this).parent().parent().data('mime');
-		if (mime !== 'httpd/unix-directory') {
+		_onClickDeleteSelected: function(event) {
 			event.preventDefault();
+			var self = this;
+			var allFiles = this.$el.find('#select_all').is(':checked');
+			var files = [];
+			var params = {};
+			if (allFiles) {
+				params = {
+					allfiles: true,
+					dir: this.getCurrentDirectory()
+				};
+			}
+			else {
+				files = _.pluck(this.getSelectedFiles(), 'name');
+				params = {
+					files: JSON.stringify(files),
+					dir: this.getCurrentDirectory()
+				};
+			}
+
+			this.disableActions();
+			if (allFiles) {
+				this.showMask();
+			}
+			else {
+				for (var i = 0; i < files.length; i++) {
+					var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete");
+					deleteAction.removeClass('delete-icon').addClass('progress-icon');
+				}
+			}
+
+			$.post(OC.filePath('files_trashbin', 'ajax', 'delete.php'),
+					params,
+					function(result) {
+						if (allFiles) {
+							if (result.status !== 'success') {
+								OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
+							}
+							self.hideMask();
+							// simply remove all files
+							self.setFiles([]);
+							self.enableActions();
+						}
+						else {
+							self._removeCallback(result);
+						}
+					}
+			);
+		},
+
+		_onClickFile: function(event) {
+			var mime = $(this).parent().parent().data('mime');
+			if (mime !== 'httpd/unix-directory') {
+				event.preventDefault();
+			}
+			return OCA.Files.FileList.prototype._onClickFile.apply(this, arguments);
+		},
+
+		generatePreviewUrl: function(urlSpec) {
+			return OC.generateUrl('/apps/files_trashbin/ajax/preview.php?') + $.param(urlSpec);
+		},
+
+		getDownloadUrl: function(action, params) {
+			// no downloads
+			return '#';
+		},
+
+		enableActions: function() {
+			this.$el.find('.action').css('display', 'inline');
+			this.$el.find(':input:checkbox').css('display', 'inline');
+		},
+
+		disableActions: function() {
+			this.$el.find('.action').css('display', 'none');
+			this.$el.find(':input:checkbox').css('display', 'none');
 		}
-		return oldClickFile.apply(this, arguments);
-	};
 
+	});
+
+	OCA.Trashbin.FileList = FileList;
 })();
+
diff --git a/apps/files_trashbin/js/files.js b/apps/files_trashbin/js/files.js
new file mode 100644
index 0000000..f46b96a
--- /dev/null
+++ b/apps/files_trashbin/js/files.js
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2014
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+(function() {
+
+	var Files = _.extend({}, OCA.Files.Files, {
+		updateStorageStatistics: function() {
+			// no op because the trashbin doesn't have
+			// storage info like free space / used space
+		}
+
+	});
+
+	OCA.Trashbin.Files = Files;
+})();
+
diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js
deleted file mode 100644
index 5f2436d..0000000
--- a/apps/files_trashbin/js/trash.js
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (c) 2014
- *
- * This file is licensed under the Affero General Public License version 3
- * or later.
- *
- * See the COPYING-README file.
- *
- */
-
-/* global OC, t, BreadCrumb, FileActions, FileList, Files */
-$(document).ready(function() {
-	var deletedRegExp = new RegExp(/^(.+)\.d[0-9]+$/);
-
-	/**
-	 * Convert a file name in the format filename.d12345 to the real file name.
-	 * This will use basename.
-	 * The name will not be changed if it has no ".d12345" suffix.
-	 * @param name file name
-	 * @return converted file name
-	 */
-	function getDeletedFileName(name) {
-		name = OC.basename(name);
-		var match = deletedRegExp.exec(name);
-		if (match && match.length > 1) {
-			name = match[1];
-		}
-		return name;
-	}
-
-	Files.updateStorageStatistics = function() {
-		// no op because the trashbin doesn't have
-		// storage info like free space / used space
-	};
-
-	if (typeof FileActions !== 'undefined') {
-		FileActions.register('all', 'Restore', OC.PERMISSION_READ, OC.imagePath('core', 'actions/history'), function(filename) {
-			var tr = FileList.findFileEl(filename);
-			var deleteAction = tr.children("td.date").children(".action.delete");
-			deleteAction.removeClass('delete-icon').addClass('progress-icon');
-			disableActions();
-			$.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'), {
-					files: JSON.stringify([filename]),
-					dir: FileList.getCurrentDirectory()
-				},
-			    FileList._removeCallback
-			);
-		}, t('files_trashbin', 'Restore'));
-	};
-
-	FileActions.register('all', 'Delete', OC.PERMISSION_READ, function() {
-		return OC.imagePath('core', 'actions/delete');
-	}, function(filename) {
-		$('.tipsy').remove();
-		var tr = FileList.findFileEl(filename);
-		var deleteAction = tr.children("td.date").children(".action.delete");
-		deleteAction.removeClass('delete-icon').addClass('progress-icon');
-		disableActions();
-		$.post(OC.filePath('files_trashbin', 'ajax', 'delete.php'), {
-				files: JSON.stringify([filename]),
-				dir: FileList.getCurrentDirectory()
-			},
-			FileList._removeCallback
-		);
-	});
-
-	/**
-	 * Override crumb URL maker (hacky!)
-	 */
-	FileList.breadcrumb.getCrumbUrl = function(part, index) {
-		if (index === 0) {
-			return OC.linkTo('files', 'index.php');
-		}
-		return OC.linkTo('files_trashbin', 'index.php')+"?dir=" + encodeURIComponent(part.dir);
-	};
-
-	Files.generatePreviewUrl = function(urlSpec) {
-		return OC.generateUrl('/apps/files_trashbin/ajax/preview.php?') + $.param(urlSpec);
-	};
-
-	Files.getDownloadUrl = function(action, params) {
-		// no downloads
-		return '#';
-	};
-
-	Files.getAjaxUrl = function(action, params) {
-		var q = '';
-		if (params) {
-			q = '?' + OC.buildQueryString(params);
-		}
-		return OC.filePath('files_trashbin', 'ajax', action + '.php') + q;
-	};
-
-
-	/**
-	 * Override crumb making to add "Deleted Files" entry
-	 * and convert files with ".d" extensions to a more
-	 * user friendly name.
-	 */
-	var oldMakeCrumbs = BreadCrumb.prototype._makeCrumbs;
-	BreadCrumb.prototype._makeCrumbs = function() {
-		var parts = oldMakeCrumbs.apply(this, arguments);
-		// duplicate first part
-		parts.unshift(parts[0]);
-		parts[1] = {
-			dir: '/',
-			name: t('files_trashbin', 'Deleted Files')
-		};
-		for (var i = 2; i < parts.length; i++) {
-			parts[i].name = getDeletedFileName(parts[i].name);
-		}
-		return parts;
-	};
-
-	FileActions.actions.dir = {
-		// only keep 'Open' action for navigation
-		'Open': FileActions.actions.dir.Open
-	};
-});
-
-function enableActions() {
-	$(".action").css("display", "inline");
-	$(":input:checkbox").css("display", "inline");
-}
-
-function disableActions() {
-	$(".action").css("display", "none");
-	$(":input:checkbox").css("display", "none");
-}
-
diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php
index e90162e..6622c1d 100644
--- a/apps/files_trashbin/templates/index.php
+++ b/apps/files_trashbin/templates/index.php
@@ -6,6 +6,8 @@
 
 <div id="emptycontent" class="hidden"><?php p($l->t('Nothing in here. Your trash bin is empty!'))?></div>
 
+<input type="hidden" name="dir" value="" id="dir">
+
 <table id="filestable">
 	<thead>
 		<tr>
diff --git a/apps/files_trashbin/tests/js/appSpec.js b/apps/files_trashbin/tests/js/appSpec.js
new file mode 100644
index 0000000..ca7d718
--- /dev/null
+++ b/apps/files_trashbin/tests/js/appSpec.js
@@ -0,0 +1,69 @@
+/**
+* ownCloud
+*
+* @author Vincent Petry
+* @copyright 2014 Vincent Petry <pvince81 at owncloud.com>
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+*
+* You should have received a copy of the GNU Affero General Public
+* License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+describe('OCA.Trashbin.App tests', function() {
+	var App = OCA.Trashbin.App;
+
+	beforeEach(function() {
+		$('#testArea').append(
+			'<div id="app-navigation">' +
+			'<ul><li data-id="files"><a>Files</a></li>' +
+			'<li data-id="trashbin"><a>Trashbin</a></li>' +
+			'</div>' +
+			'<div id="app-content">' +
+			'<div id="app-content-files" class="hidden">' +
+			'</div>' +
+			'<div id="app-content-trashbin" class="hidden">' +
+			'</div>' +
+			'</div>' +
+			'</div>'
+		);
+		App.initialize($('#app-content-trashbin'));
+	});
+	afterEach(function() {
+		App._initialized = false;
+		App.fileList = null;
+	});
+
+	describe('initialization', function() {
+		it('creates a custom filelist instance', function() {
+			App.initialize();
+			expect(App.fileList).toBeDefined();
+			expect(App.fileList.$el.is('#app-content-trashbin')).toEqual(true);
+		});
+
+		it('registers custom file actions', function() {
+			var fileActions;
+			App.initialize();
+
+			fileActions = App.fileList.fileActions;
+
+			expect(fileActions.actions.all).toBeDefined();
+			expect(fileActions.actions.all.Restore).toBeDefined();
+			expect(fileActions.actions.all.Delete).toBeDefined();
+
+			expect(fileActions.actions.all.Rename).not.toBeDefined();
+			expect(fileActions.actions.all.Download).not.toBeDefined();
+
+			expect(fileActions.defaults.dir).toEqual('Open');
+		});
+	});
+});
diff --git a/apps/files_trashbin/tests/js/filelistSpec.js b/apps/files_trashbin/tests/js/filelistSpec.js
new file mode 100644
index 0000000..291b2ff
--- /dev/null
+++ b/apps/files_trashbin/tests/js/filelistSpec.js
@@ -0,0 +1,219 @@
+/**
+* ownCloud
+*
+* @author Vincent Petry
+* @copyright 2014 Vincent Petry <pvince81 at owncloud.com>
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+*
+* You should have received a copy of the GNU Affero General Public
+* License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+describe('OCA.Trashbin.FileList tests', function() {
+	var testFiles, alertStub, notificationStub, fileList;
+	var FileActions = OCA.Files.FileActions;
+
+	beforeEach(function() {
+		// init horrible parameters
+		var $body = $('body');
+		$body.append('<input type="hidden" id="dir" value="/"></input>');
+		// dummy files table
+		$body.append('<table id="filestable"></table>');
+
+		alertStub = sinon.stub(OC.dialogs, 'alert');
+		notificationStub = sinon.stub(OC.Notification, 'show');
+
+		// init parameters and test table elements
+		$('#testArea').append(
+			'<div id="app-content-trashbin">' +
+			'<input type="hidden" id="dir" value="/"></input>' +
+			'<input type="hidden" id="permissions" value="31"></input>' +
+			// dummy controls
+			'<div id="controls">' +
+			'   <div class="actions creatable"></div>' +
+			'   <div class="notCreatable"></div>' +
+			'</div>' +
+			// dummy table
+			// TODO: at some point this will be rendered by the fileList class itself!
+			'<table id="filestable">' +
+			'<thead><tr><th id="headerName" class="hidden">' +
+			'<input type="checkbox" id="select_all">' +
+			'<span class="name">Name</span>' +
+			'<span class="selectedActions hidden">' +
+			'<a href class="undelete">Restore</a>' +
+			'<a href class="delete-selected">Delete</a></span>' +
+			'</th></tr></thead>' +
+		   	'<tbody id="fileList"></tbody>' +
+			'<tfoot></tfoot>' +
+			'</table>' +
+			'<div id="emptycontent">Empty content message</div>' +
+			'</div>'
+		);
+
+		testFiles = [{
+			id: 1,
+			type: 'file',
+			name: 'One.txt',
+			mtime: 11111000,
+			mimetype: 'text/plain',
+			size: 12,
+			etag: 'abc'
+		}, {
+			id: 2,
+			type: 'file',
+			name: 'Two.jpg',
+			mtime: 22222000,
+			mimetype: 'image/jpeg',
+			size: 12049,
+			etag: 'def',
+		}, {
+			id: 3,
+			type: 'file',
+			name: 'Three.pdf',
+			mtime: 33333000,
+			mimetype: 'application/pdf',
+			size: 58009,
+			etag: '123',
+		}, {
+			id: 4,
+			type: 'dir',
+			mtime: 99999000,
+			name: 'somedir',
+			mimetype: 'httpd/unix-directory',
+			size: 250,
+			etag: '456'
+		}];
+
+		fileList = new OCA.Trashbin.FileList($('#app-content-trashbin'));
+		OCA.Trashbin.App.registerFileActions(fileList);
+	});
+	afterEach(function() {
+		testFiles = undefined;
+		fileList = undefined;
+
+		FileActions.clear();
+		$('#dir').remove();
+		notificationStub.restore();
+		alertStub.restore();
+	});
+	describe('Rendering rows', function() {
+		// TODO. test that rows show the correct name but
+		// have the real file name with the ".d" suffix
+		// TODO: with and without dir listing
+	});
+	describe('File actions', function() {
+		describe('Deleting single files', function() {
+			// TODO: checks ajax call
+			// TODO: checks spinner
+			// TODO: remove item after delete
+			// TODO: bring back item if delete failed
+		});
+		describe('Restoring single files', function() {
+			// TODO: checks ajax call
+			// TODO: checks spinner
+			// TODO: remove item after restore
+			// TODO: bring back item if restore failed
+		});
+	});
+	describe('file previews', function() {
+		// TODO: check that preview URL is going through files_trashbin
+	});
+	describe('loading file list', function() {
+		// TODO: check that ajax URL is going through files_trashbin
+	});
+	describe('breadcrumbs', function() {
+		// TODO: test label + URL
+	});
+	describe('Global Actions', function() {
+		beforeEach(function() {
+			fileList.setFiles(testFiles);
+			fileList.findFileEl('One.txt.d11111').find('input:checkbox').click();
+			fileList.findFileEl('Three.pdf.d33333').find('input:checkbox').click();
+			fileList.findFileEl('somedir.d99999').find('input:checkbox').click();
+		});
+		describe('Delete', function() {
+			// TODO: also test with "allFiles"
+			it('Deletes selected files when "Delete" clicked', function() {
+				var request;
+				$('.selectedActions .delete-selected').click();
+				expect(fakeServer.requests.length).toEqual(1);
+				request = fakeServer.requests[0];
+				expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_trashbin/ajax/delete.php');
+				expect(OC.parseQueryString(request.requestBody))
+					.toEqual({'dir': '/', files: '["One.txt.d11111","Three.pdf.d33333","somedir.d99999"]'});
+				fakeServer.requests[0].respond(
+					200,
+					{ 'Content-Type': 'application/json' },
+					JSON.stringify({status: 'success'})
+				);
+				expect(fileList.findFileEl('One.txt.d11111').length).toEqual(0);
+				expect(fileList.findFileEl('Three.pdf.d33333').length).toEqual(0);
+				expect(fileList.findFileEl('somedir.d99999').length).toEqual(0);
+				expect(fileList.findFileEl('Two.jpg.d22222').length).toEqual(1);
+			});
+			it('Deletes all files when all selected when "Delete" clicked', function() {
+				var request;
+				$('#select_all').click();
+				$('.selectedActions .delete-selected').click();
+				expect(fakeServer.requests.length).toEqual(1);
+				request = fakeServer.requests[0];
+				expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_trashbin/ajax/delete.php');
+				expect(OC.parseQueryString(request.requestBody))
+					.toEqual({'dir': '/', allfiles: 'true'});
+				fakeServer.requests[0].respond(
+					200,
+					{ 'Content-Type': 'application/json' },
+					JSON.stringify({status: 'success'})
+				);
+				expect(fileList.isEmpty).toEqual(true);
+			});
+		});
+		describe('Restore', function() {
+			// TODO: also test with "allFiles"
+			it('Restores selected files when "Restore" clicked', function() {
+				var request;
+				$('.selectedActions .undelete').click();
+				expect(fakeServer.requests.length).toEqual(1);
+				request = fakeServer.requests[0];
+				expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_trashbin/ajax/undelete.php');
+				expect(OC.parseQueryString(request.requestBody))
+					.toEqual({'dir': '/', files: '["One.txt.d11111","Three.pdf.d33333","somedir.d99999"]'});
+				fakeServer.requests[0].respond(
+					200,
+					{ 'Content-Type': 'application/json' },
+					JSON.stringify({status: 'success'})
+				);
+				expect(fileList.findFileEl('One.txt').length).toEqual(0);
+				expect(fileList.findFileEl('Three.pdf').length).toEqual(0);
+				expect(fileList.findFileEl('somedir').length).toEqual(0);
+				expect(fileList.findFileEl('Two.jpg').length).toEqual(1);
+			});
+			it('Restores all files when all selected when "Restore" clicked', function() {
+				var request;
+				$('#select_all').click();
+				$('.selectedActions .undelete').click();
+				expect(fakeServer.requests.length).toEqual(1);
+				request = fakeServer.requests[0];
+				expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_trashbin/ajax/undelete.php');
+				expect(OC.parseQueryString(request.requestBody))
+					.toEqual({'dir': '/', allfiles: 'true'});
+				fakeServer.requests[0].respond(
+					200,
+					{ 'Content-Type': 'application/json' },
+					JSON.stringify({status: 'success'})
+				);
+				expect(fileList.isEmpty).toEqual(true);
+			});
+		});
+	});
+});
diff --git a/core/js/js.js b/core/js/js.js
index 93f4196..38b9759 100644
--- a/core/js/js.js
+++ b/core/js/js.js
@@ -1319,6 +1319,114 @@ OC.Util = {
 };
 
 /**
+ * Utility class for the history API,
+ * includes fallback to using the URL hash when
+ * the browser doesn't support the history API.
+ */
+OC.Util.History = {
+	_handlers: [],
+
+	/**
+	 * Push the current URL parameters to the history stack
+	 * and change the visible URL.
+	 * Note: this includes a workaround for IE8/IE9 that uses
+	 * the hash part instead of the search part.
+	 *
+	 * @param params to append to the URL, can be either a string
+	 * or a map
+	 */
+	pushState: function(params) {
+		var strParams;
+		if (typeof(params) === 'string') {
+			strParams = params;
+		}
+		else {
+			strParams = OC.buildQueryString(params);
+		}
+		if (window.history.pushState) {
+			var url = location.pathname + '?' + strParams;
+			window.history.pushState(params, '', url);
+		}
+		// use URL hash for IE8
+		else {
+			window.location.hash = '?' + strParams;
+			// inhibit next onhashchange that just added itself
+			// to the event queue
+			this._cancelPop = true;
+		}
+	},
+
+	/**
+	 * Add a popstate handler
+	 *
+	 * @param handler function
+	 */
+	addOnPopStateHandler: function(handler) {
+		this._handlers.push(handler);
+	},
+
+	/**
+	 * Parse a query string from the hash part of the URL.
+	 * (workaround for IE8 / IE9)
+	 */
+	_parseHashQuery: function() {
+		var hash = window.location.hash,
+			pos = hash.indexOf('?');
+		if (pos >= 0) {
+			return hash.substr(pos + 1);
+		}
+		return '';
+	},
+
+	_decodeQuery: function(query) {
+		return query.replace(/\+/g, ' ');
+	},
+
+	/**
+	 * Parse the query/search part of the URL.
+	 * Also try and parse it from the URL hash (for IE8)
+	 *
+	 * @return map of parameters
+	 */
+	parseUrlQuery: function() {
+		var query = this._parseHashQuery(),
+			params;
+		// try and parse from URL hash first
+		if (query) {
+			params = OC.parseQueryString(this._decodeQuery(query));
+		}
+		// else read from query attributes
+		if (!params) {
+			params = OC.parseQueryString(this._decodeQuery(location.search));
+		}
+		return params || {};
+	},
+
+	_onPopState: function(e) {
+		if (this._cancelPop) {
+			this._cancelPop = false;
+			return;
+		}
+		var params;
+		if (!this._handlers.length) {
+			return;
+		}
+		params = (e && e.state) || this.parseUrlQuery() || {};
+		for (var i = 0; i < this._handlers.length; i++) {
+			this._handlers[i](params);
+		}
+	}
+};
+
+// fallback to hashchange when no history support
+if (window.history.pushState) {
+	window.onpopstate = _.bind(OC.Util.History._onPopState, OC.Util.History);
+}
+else {
+	$(window).on('hashchange', _.bind(OC.Util.History._onPopState, OC.Util.History));
+}
+
+/**
  * Get a variable by name
  * @param {string} name
  * @return {*}
diff --git a/tests/karma.config.js b/tests/karma.config.js
index 338e3f8..08b49d8 100644
--- a/tests/karma.config.js
+++ b/tests/karma.config.js
@@ -43,7 +43,7 @@ module.exports = function(config) {
 		return apps;
 		*/
 		// other apps tests don't run yet... needs further research / clean up
-		return ['files'];
+		return ['files', 'files_trashbin'];
 	}
 
 	// respect NOCOVERAGE env variable

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-owncloud/owncloud.git



More information about the Pkg-owncloud-commits mailing list