From 9c2fbea6a4396a29ce8c966c9ea7646aa8fc9be5 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 12 Feb 2014 14:50:23 +0100 Subject: [PATCH] Fix file selection for infinite scrolling - moved file selection code to FileList - fix selection summary when all files are selected - nextPage now auto-selects files if "select all" checkbox is checked - fixed trashbin to use the same selection logic as FileList --- apps/files/css/files.css | 1 - apps/files/js/filelist.js | 266 ++++++++++++++++++++++++++-- apps/files/js/files.js | 202 +-------------------- apps/files/tests/js/filelistSpec.js | 217 ++++++++++++++++++++++- apps/files/tests/js/filesSpec.js | 28 ++- apps/files_trashbin/js/filelist.js | 126 +++++++++++++ apps/files_trashbin/js/trash.js | 163 +---------------- core/js/js.js | 2 +- 8 files changed, 626 insertions(+), 379 deletions(-) diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 474f1af072..533050691d 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -310,7 +310,6 @@ a.action>img { max-height:16px; max-width:16px; vertical-align:text-bottom; } /* Actions for selected files */ .selectedActions { - display: none; position: absolute; top: -1px; right: 0; diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 223f4bb440..02754d7acb 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -9,7 +9,7 @@ */ /* global OC, t, n, FileList, FileActions, Files, FileSummary, BreadCrumb */ -/* global procesSelection, dragOptions, folderDropOptions */ +/* global dragOptions, folderDropOptions */ window.FileList = { appName: t('files', 'Files'), isEmpty: true, @@ -60,6 +60,142 @@ window.FileList = { var width = $(this).width(); FileList.breadcrumb.resize(width, false); }); + + this.$fileList.on('click','td.filename a', this._onClickFile); + this.$fileList.on('change', 'td.filename input:checkbox', this._onClickFileCheckbox); + this.$el.find('#select_all').click(this._onClickSelectAll); + this.$el.find('.download').click(this._onClickDownloadSelected); + this.$el.find('.delete-selected').click(this._onClickDeleteSelected); + }, + + /** + * Event handler for when clicking on files to select them + */ + _onClickFile: function(event) { + if (event.ctrlKey || event.shiftKey) { + event.preventDefault(); + if (event.shiftKey) { + var last = $(FileList._lastChecked).parent().parent().prevAll().length; + var first = $(this).parent().parent().prevAll().length; + var start = Math.min(first, last); + var end = Math.max(first, last); + var rows = $(this).parent().parent().parent().children('tr'); + for (var i = start; i < end; i++) { + $(rows).each(function(index) { + if (index === i) { + var checkbox = $(this).children().children('input:checkbox'); + $(checkbox).attr('checked', 'checked'); + $(checkbox).parent().parent().addClass('selected'); + } + }); + } + } + var checkbox = $(this).parent().children('input:checkbox'); + FileList._lastChecked = checkbox; + if ($(checkbox).attr('checked')) { + $(checkbox).removeAttr('checked'); + $(checkbox).parent().parent().removeClass('selected'); + $('#select_all').removeAttr('checked'); + } else { + $(checkbox).attr('checked', 'checked'); + $(checkbox).parent().parent().toggleClass('selected'); + var selectedCount = $('td.filename input:checkbox:checked').length; + if (selectedCount === $('td.filename input:checkbox').length) { + $('#select_all').attr('checked', 'checked'); + } + } + FileList.updateSelectionSummary(); + } else { + var filename=$(this).parent().parent().attr('data-file'); + var tr = FileList.findFileEl(filename); + var renaming=tr.data('renaming'); + if (!renaming) { + FileActions.currentFile = $(this).parent(); + 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 a file's checkbox + */ + _onClickFileCheckbox: function(event) { + // FIXME: not sure what the difference is supposed to be with FileList._onClickFile + if (event.shiftKey) { + var last = $(FileList._lastChecked).parent().parent().prevAll().length; + var first = $(this).parent().parent().prevAll().length; + var start = Math.min(first, last); + var end = Math.max(first, last); + var rows = $(this).parent().parent().parent().children('tr'); + for (var i = start; i < end; i++) { + $(rows).each(function(index) { + if (index === i) { + var checkbox = $(this).children().children('input:checkbox'); + $(checkbox).attr('checked', 'checked'); + $(checkbox).parent().parent().addClass('selected'); + } + }); + } + } + var selectedCount=$('td.filename input:checkbox:checked').length; + $(this).parent().parent().toggleClass('selected'); + if (!$(this).attr('checked')) { + $('#select_all').attr('checked',false); + } else { + if (selectedCount===$('td.filename input:checkbox').length) { + $('#select_all').attr('checked',true); + } + } + FileList.updateSelectionSummary(); + }, + + /** + * Event handler for when selecting/deselecting all files + */ + _onClickSelectAll: function(e) { + var checked = $(this).prop('checked'); + FileList.$fileList.find('td.filename input:checkbox').prop('checked', checked) + .parent().parent().toggleClass('selected', checked); + FileList.updateSelectionSummary(); + }, + + /** + * Event handler for when clicking on "Download" for the selected files + */ + _onClickDownloadSelected: function(event) { + var files; + var dir = FileList.getCurrentDirectory(); + if (FileList.isAllSelected()) { + files = OC.basename(dir); + dir = OC.dirname(dir) || '/'; + } + else { + files = FileList.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; + }, + + /** + * Event handler for when clicking on "Delete" for the selected files + */ + _onClickDeleteSelected: function(event) { + var files = null; + if (!FileList.isAllSelected()) { + files = FileList.getSelectedFiles('name'); + } + FileList.do_delete(files); + event.preventDefault(); + return false; }, /** @@ -79,7 +215,7 @@ window.FileList = { if (this.pageNumber + 1 >= this.totalPages) { return; } - if ($(window).scrollTop() + $(window).height() > $(document).height() - 20) { + if ($(window).scrollTop() + $(window).height() > $(document).height() - 500) { this._nextPage(true); } }, @@ -113,7 +249,7 @@ window.FileList = { if (result) { if (result.status === 'success') { FileList.remove(file); - procesSelection(); + FileList.updateSelectionSummary(); $('#notification').hide(); } else { $('#notification').hide(); @@ -152,13 +288,30 @@ window.FileList = { 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){ + 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 tr, index, count = this.pageSize, - newTrs = []; + newTrs = [], + selected = this.isAllSelected(); if (this.pageNumber + 1 >= this.totalPages) { return; @@ -169,6 +322,10 @@ window.FileList = { while (count > 0 && index < this.files.length) { tr = this.add(this.files[index], {updateSummary: false}); + if (selected) { + tr.addClass('selected'); + tr.find('input:checkbox').prop('checked', true); + } if (animate) { tr.addClass('appear transparent'); // TODO newTrs.push(tr); @@ -201,6 +358,9 @@ window.FileList = { this.$fileList.detach(); this.$fileList.empty(); + // clear "Select all" checkbox + $('#select_all').prop('checked', false); + this.isEmpty = this.files.length === 0; this._nextPage(); @@ -215,7 +375,7 @@ window.FileList = { this.fileSummary.calculate(filesArray); - procesSelection(); + FileList.updateSelectionSummary(); $(window).scrollTop(0); this.$fileList.trigger(jQuery.Event("updated")); @@ -580,10 +740,14 @@ window.FileList = { * @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); + if (!fileEl.length) { + return null; + } if (fileEl.data('permissions') & OC.PERMISSION_DELETE) { // file is only draggable when delete permissions are set fileEl.find('td.filename').draggable('destroy'); @@ -824,21 +988,22 @@ window.FileList = { function(result) { if (result.status === 'success') { if (params.allfiles) { - // clear whole list - $('#fileList tr').remove(); + 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')}); }); } - procesSelection(); checkTrashStatus(); FileList.updateEmptyContent(); FileList.fileSummary.update(); + FileList.updateSelectionSummary(); Files.updateStorageStatistics(); } else { if (result.status === 'error' && result.data.message) { @@ -941,14 +1106,95 @@ window.FileList = { $(e).removeClass("searchresult"); }); }, + /** + * Update UI based on the current selection + */ + updateSelectionSummary: function() { + var allSelected = this.isAllSelected(); + var selected; + var summary = { + totalFiles: 0, + totalDirs: 0, + totalSize: 0 + }; + + if (allSelected) { + summary = this.fileSummary.summary; + } + else { + selected = this.getSelectedFiles(); + for (var i = 0; i < selected.length; i++ ){ + if (selected[i].type === 'dir') { + summary.totalDirs++; + } + else { + summary.totalFiles++; + } + summary.totalSize += parseInt(selected[i].size, 10) || 0; + } + } + if (summary.totalFiles === 0 && summary.totalDirs === 0) { + $('#headerName span.name').text(t('files','Name')); + $('#headerSize').text(t('files','Size')); + $('#modified').text(t('files','Modified')); + $('table').removeClass('multiselect'); + $('.selectedActions').addClass('hidden'); + $('#select_all').removeAttr('checked'); + } + else { + $('.selectedActions').removeClass('hidden'); + $('#headerSize').text(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 += n('files', '%n file', '%n files', summary.totalFiles); + } + $('#headerName span.name').text(selection); + $('#modified').text(''); + $('table').addClass('multiselect'); + } + }, + /** * Returns whether all files are selected * @return true if all files are selected, false otherwise */ isAllSelected: function() { - return $('#select_all').prop('checked'); + return this.$el.find('#select_all').prop('checked'); + }, + + /** + * @brief get a list of selected files + * @param {string} property (option) the property of the file requested + * @return {array} + * + * possible values for property: name, mime, size and type + * if property is set, an array with that property for each file is returnd + * if it's ommited an array of objects with all properties is returned + */ + getSelectedFiles: function(property) { + var elements=$('td.filename input:checkbox:checked').parent().parent(); + var files=[]; + elements.each(function(i,element) { + // TODO: make the json format the same as in FileList.add() + var file = FileList.elementToFile($(element)); + // FIXME: legacy attributes + file.origin = file.id; + file.mime = file.mimetype; + if (property) { + files.push(file[property]); + } else { + files.push(file); + } + }); + return files; } -}; +} $(document).ready(function() { FileList.initialize(); diff --git a/apps/files/js/files.js b/apps/files/js/files.js index 5e669a796a..6cb0d41a61 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -209,7 +209,7 @@ $(document).ready(function() { // Trigger cancelling of file upload $('#uploadprogresswrapper .stop').on('click', function() { OC.Upload.cancelUploads(); - procesSelection(); + FileList.updateSelectionSummary(); }); // Show trash bin @@ -217,130 +217,6 @@ $(document).ready(function() { window.location=OC.filePath('files_trashbin', '', 'index.php'); }); - var lastChecked; - - // Sets the file link behaviour : - $('#fileList').on('click','td.filename a',function(event) { - if (event.ctrlKey || event.shiftKey) { - event.preventDefault(); - if (event.shiftKey) { - var last = $(lastChecked).parent().parent().prevAll().length; - var first = $(this).parent().parent().prevAll().length; - var start = Math.min(first, last); - var end = Math.max(first, last); - var rows = $(this).parent().parent().parent().children('tr'); - for (var i = start; i < end; i++) { - $(rows).each(function(index) { - if (index === i) { - var checkbox = $(this).children().children('input:checkbox'); - $(checkbox).attr('checked', 'checked'); - $(checkbox).parent().parent().addClass('selected'); - } - }); - } - } - var checkbox = $(this).parent().children('input:checkbox'); - lastChecked = checkbox; - if ($(checkbox).attr('checked')) { - $(checkbox).removeAttr('checked'); - $(checkbox).parent().parent().removeClass('selected'); - $('#select_all').removeAttr('checked'); - } else { - $(checkbox).attr('checked', 'checked'); - $(checkbox).parent().parent().toggleClass('selected'); - var selectedCount = $('td.filename input:checkbox:checked').length; - if (selectedCount === $('td.filename input:checkbox').length) { - $('#select_all').attr('checked', 'checked'); - } - } - procesSelection(); - } else { - var filename=$(this).parent().parent().attr('data-file'); - var tr = FileList.findFileEl(filename); - var renaming=tr.data('renaming'); - if (!renaming) { - FileActions.currentFile = $(this).parent(); - 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); - } - } - } - - }); - - // Sets the select_all checkbox behaviour : - $('#select_all').click(function() { - if ($(this).attr('checked')) { - // Check all - $('td.filename input:checkbox').attr('checked', true); - $('td.filename input:checkbox').parent().parent().addClass('selected'); - } else { - // Uncheck all - $('td.filename input:checkbox').attr('checked', false); - $('td.filename input:checkbox').parent().parent().removeClass('selected'); - } - procesSelection(); - }); - - $('#fileList').on('change', 'td.filename input:checkbox',function(event) { - if (event.shiftKey) { - var last = $(lastChecked).parent().parent().prevAll().length; - var first = $(this).parent().parent().prevAll().length; - var start = Math.min(first, last); - var end = Math.max(first, last); - var rows = $(this).parent().parent().parent().children('tr'); - for (var i = start; i < end; i++) { - $(rows).each(function(index) { - if (index === i) { - var checkbox = $(this).children().children('input:checkbox'); - $(checkbox).attr('checked', 'checked'); - $(checkbox).parent().parent().addClass('selected'); - } - }); - } - } - var selectedCount=$('td.filename input:checkbox:checked').length; - $(this).parent().parent().toggleClass('selected'); - if (!$(this).attr('checked')) { - $('#select_all').attr('checked',false); - } else { - if (selectedCount===$('td.filename input:checkbox').length) { - $('#select_all').attr('checked',true); - } - } - procesSelection(); - }); - - $('.download').click('click',function(event) { - var files; - var dir = FileList.getCurrentDirectory(); - if (FileList.isAllSelected()) { - files = OC.basename(dir); - dir = OC.dirname(dir) || '/'; - } - else { - files = Files.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; - }); - - $('.delete-selected').click(function(event) { - var files = Files.getSelectedFiles('name'); - event.preventDefault(); - if (FileList.isAllSelected()) { - files = null; - } - FileList.do_delete(files); - return false; - }); - // drag&drop support using jquery.fileupload // TODO use OC.dialogs $(document).bind('drop dragover', function (e) { @@ -440,7 +316,7 @@ var createDragShadow = function(event) { $(event.target).parents('tr').find('td input:first').prop('checked',true); } - var selectedFiles = Files.getSelectedFiles(); + var selectedFiles = FileList.getSelectedFiles(); if (!isDragSelected && selectedFiles.length === 1) { //revert the selection @@ -539,7 +415,7 @@ var folderDropOptions={ oldFile.find('td.filesize').text(humanFileSize(newSize)); FileList.remove(file); - procesSelection(); + FileList.updateSelectionSummary(); $('#notification').hide(); } else { $('#notification').hide(); @@ -556,78 +432,6 @@ var folderDropOptions={ tolerance: 'pointer' }; -function procesSelection() { - var selected = Files.getSelectedFiles(); - var selectedFiles = selected.filter(function(el) { - return el.type==='file'; - }); - var selectedFolders = selected.filter(function(el) { - return el.type==='dir'; - }); - if (selectedFiles.length === 0 && selectedFolders.length === 0) { - $('#headerName span.name').text(t('files','Name')); - $('#headerSize').text(t('files','Size')); - $('#modified').text(t('files','Modified')); - $('table').removeClass('multiselect'); - $('.selectedActions').hide(); - $('#select_all').removeAttr('checked'); - } - else { - $('.selectedActions').show(); - var totalSize = 0; - for(var i=0; i 0) { - selection += n('files', '%n folder', '%n folders', selectedFolders.length); - if (selectedFiles.length > 0) { - selection += ' & '; - } - } - if (selectedFiles.length>0) { - selection += n('files', '%n file', '%n files', selectedFiles.length); - } - $('#headerName span.name').text(selection); - $('#modified').text(''); - $('table').addClass('multiselect'); - } -} - -/** - * @brief get a list of selected files - * @param {string} property (option) the property of the file requested - * @return {array} - * - * possible values for property: name, mime, size and type - * if property is set, an array with that property for each file is returnd - * if it's ommited an array of objects with all properties is returned - */ -Files.getSelectedFiles = function(property) { - var elements=$('td.filename input:checkbox:checked').parent().parent(); - var files=[]; - elements.each(function(i,element) { - var file={ - name:$(element).attr('data-file'), - mime:$(element).data('mime'), - type:$(element).data('type'), - size:$(element).data('size'), - etag:$(element).data('etag'), - origin: $(element).data('id') - }; - if (property) { - files.push(file[property]); - } else { - files.push(file); - } - }); - return files; -} - Files.getMimeIcon = function(mime, ready) { if (Files.getMimeIcon.cache[mime]) { ready(Files.getMimeIcon.cache[mime]); diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index 6e80d78eee..93e7c81cb1 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -48,8 +48,15 @@ describe('FileList tests', function() { '
' + '' + // dummy table + // TODO: at some point this will be rendered by the FileList class itself! '' + - '' + + '' + '' + '' + '
' + @@ -61,25 +68,29 @@ describe('FileList tests', function() { type: 'file', name: 'One.txt', mimetype: 'text/plain', - size: 12 + size: 12, + etag: 'abc' }, { id: 2, type: 'file', name: 'Two.jpg', mimetype: 'image/jpeg', - size: 12049 + size: 12049, + etag: 'def', }, { id: 3, type: 'file', name: 'Three.pdf', mimetype: 'application/pdf', - size: 58009 + size: 58009, + etag: '123', }, { id: 4, type: 'dir', name: 'somedir', mimetype: 'httpd/unix-directory', - size: 250 + size: 250, + etag: '456' }]; FileList.initialize(); @@ -380,7 +391,7 @@ describe('FileList tests', function() { $input.val('One_renamed.txt').blur(); expect(fakeServer.requests.length).toEqual(1); - var request = fakeServer.requests[0]; + request = fakeServer.requests[0]; expect(request.url.substr(0, request.url.indexOf('?'))).toEqual(OC.webroot + '/index.php/apps/files/ajax/rename.php'); expect(OC.parseQueryString(request.url)).toEqual({'dir': '/subdir', newname: 'One_renamed.txt', file: 'One.txt'}); @@ -519,6 +530,16 @@ describe('FileList tests', function() { FileList.setFiles(testFiles); expect(handler.calledOnce).toEqual(true); }); + it('does not update summary when removing non-existing files', function() { + // single file + 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'); + expect($summary.hasClass('hidden')).toEqual(false); + expect($summary.find('.info').text()).toEqual('0 folders and 1 file'); + }); }); describe('file previews', function() { var previewLoadStub; @@ -811,4 +832,188 @@ describe('FileList tests', 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'); }); }); + describe('File selection', function() { + beforeEach(function() { + FileList.setFiles(testFiles); + }); + it('Selects a file when clicking its checkbox', function() { + 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('Selecting all files will automatically check "select all" checkbox', function() { + expect($('#select_all').prop('checked')).toEqual(false); + $('#fileList tr td.filename input:checkbox').click(); + expect($('#select_all').prop('checked')).toEqual(true); + }); + it('Clicking "select all" will select/deselect all files', function() { + $('#select_all').click(); + expect($('#select_all').prop('checked')).toEqual(true); + $('#fileList tr input:checkbox').each(function() { + expect($(this).prop('checked')).toEqual(true); + }); + + $('#select_all').click(); + expect($('#select_all').prop('checked')).toEqual(false); + + $('#fileList tr input:checkbox').each(function() { + expect($(this).prop('checked')).toEqual(false); + }); + }); + 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'); + $tr.find('input:checkbox').click(); + + expect($('#select_all').prop('checked')).toEqual(false); + }); + it('Selecting files updates selection summary', function() { + var $summary = $('#headerName span.name'); + 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(); + expect($summary.text()).toEqual('1 folder & 2 files'); + }); + it('Unselecting files hides selection summary', function() { + var $summary = $('#headerName span.name'); + 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'); + expect($actions.hasClass('hidden')).toEqual(true); + $checkbox.click(); + expect($actions.hasClass('hidden')).toEqual(false); + $checkbox.click(); + expect($actions.hasClass('hidden')).toEqual(true); + }); + it('Selection is cleared when switching dirs', function() { + $('#select_all').click(); + var data = { + status: 'success', + data: { + files: testFiles, + permissions: 31 + } + }; + fakeServer.respondWith(/\/index\.php\/apps\/files\/ajax\/list.php/, [ + 200, { + "Content-Type": "application/json" + }, + JSON.stringify(data) + ]); + FileList.changeDirectory('/'); + fakeServer.respond(); + expect($('#select_all').prop('checked')).toEqual(false); + }); + 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(); + }); + it('getSelectedFiles returns the selected files', function() { + var files = FileList.getSelectedFiles(); + expect(files.length).toEqual(3); + expect(files[0]).toEqual({ + id: 1, + name: 'One.txt', + mime: 'text/plain', + mimetype: 'text/plain', + type: 'file', + size: 12, + etag: 'abc', + origin: 1 + }); + expect(files[1]).toEqual({ + id: 3, + type: 'file', + name: 'Three.pdf', + mime: 'application/pdf', + mimetype: 'application/pdf', + size: 58009, + etag: '123', + origin: 3 + }); + expect(files[2]).toEqual({ + id: 4, + type: 'dir', + name: 'somedir', + mime: 'httpd/unix-directory', + mimetype: 'httpd/unix-directory', + size: 250, + etag: '456', + origin: 4 + }); + }); + describe('Download', function() { + it('Opens download URL when clicking "Download"', function() { + var redirectStub = sinon.stub(OC, 'redirect'); + $('.selectedActions .download').click(); + expect(redirectStub.calledOnce).toEqual(true); + expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=%5B%22One.txt%22%2C%22Three.pdf%22%2C%22somedir%22%5D'); + redirectStub.restore(); + }); + it('Downloads root folder when all selected in root folder', function() { + $('#dir').val('/'); + $('#select_all').click(); + var redirectStub = sinon.stub(OC, 'redirect'); + $('.selectedActions .download').click(); + expect(redirectStub.calledOnce).toEqual(true); + expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files='); + redirectStub.restore(); + }); + it('Downloads parent folder when all selected in subfolder', function() { + $('#select_all').click(); + var redirectStub = sinon.stub(OC, 'redirect'); + $('.selectedActions .download').click(); + expect(redirectStub.calledOnce).toEqual(true); + expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=subdir'); + redirectStub.restore(); + }); + }); + describe('Delete', function() { + 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/ajax/delete.php'); + expect(OC.parseQueryString(request.requestBody)) + .toEqual({'dir': '/subdir', files: '["One.txt","Three.pdf","somedir"]'}); + 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('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/ajax/delete.php'); + expect(OC.parseQueryString(request.requestBody)) + .toEqual({'dir': '/subdir', allfiles: 'true'}); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({status: 'success'}) + ); + expect(FileList.isEmpty).toEqual(true); + }); + }); + }); + }); }); diff --git a/apps/files/tests/js/filesSpec.js b/apps/files/tests/js/filesSpec.js index 018c8ef0f3..7f8848619f 100644 --- a/apps/files/tests/js/filesSpec.js +++ b/apps/files/tests/js/filesSpec.js @@ -19,7 +19,7 @@ * */ -/* global Files */ +/* global OC, Files */ describe('Files tests', function() { describe('File name validation', function() { it('Validates correct file names', function() { @@ -82,4 +82,30 @@ 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'); + }); + it('returns the ajax download URL when filename and root dir specific', function() { + var url = Files.getDownloadUrl('test file.txt', '/'); + expect(url).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=test%20file.txt'); + }); + it('returns the ajax download URL when multiple files specified', function() { + var url = Files.getDownloadUrl(['test file.txt', 'abc.txt'], '/subdir'); + expect(url).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=%5B%22test%20file.txt%22%2C%22abc.txt%22%5D'); + }); + }); }); diff --git a/apps/files_trashbin/js/filelist.js b/apps/files_trashbin/js/filelist.js index 7795daf277..6c9b345086 100644 --- a/apps/files_trashbin/js/filelist.js +++ b/apps/files_trashbin/js/filelist.js @@ -75,4 +75,130 @@ $('#emptycontent').toggleClass('hidden', exists); $('#filestable th').toggleClass('hidden', !exists); }; + + var oldInit = FileList.initialize; + FileList.initialize = function() { + var result = oldInit.apply(this, arguments); + $('.undelete').click('click', FileList._onClickRestoreSelected); + return result; + }; + + FileList._removeCallback = function(result) { + if (result.status !== 'success') { + OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); + } + + 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(); + } + + 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 = FileList.getSelectedFiles('name'); + 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'); + } + params = { + files: JSON.stringify(files), + dir: FileList.getCurrentDirectory() + }; + } + + $.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.update(''); + enableActions(); + } + else { + FileList._removeCallback(result); + } + } + ); + }; + + 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 = 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'); + } + } + + $.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')); + } + FileList.hideMask(); + // simply remove all files + FileList.setFiles([]); + enableActions(); + } + else { + FileList._removeCallback(result); + } + } + ); + }; + + var oldClickFile = FileList._onClickFile; + FileList._onClickFile = function(event) { + var mime = $(this).parent().parent().data('mime'); + if (mime !== 'httpd/unix-directory') { + event.preventDefault(); + } + return oldClickFile.apply(this, arguments); + }; + })(); diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js index 4ed5ba1c76..5f2436de80 100644 --- a/apps/files_trashbin/js/trash.js +++ b/apps/files_trashbin/js/trash.js @@ -28,22 +28,6 @@ $(document).ready(function() { return name; } - function removeCallback(result) { - if (result.status !== 'success') { - OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); - } - - 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(); - } - Files.updateStorageStatistics = function() { // no op because the trashbin doesn't have // storage info like free space / used space @@ -59,7 +43,7 @@ $(document).ready(function() { files: JSON.stringify([filename]), dir: FileList.getCurrentDirectory() }, - removeCallback + FileList._removeCallback ); }, t('files_trashbin', 'Restore')); }; @@ -76,153 +60,10 @@ $(document).ready(function() { files: JSON.stringify([filename]), dir: FileList.getCurrentDirectory() }, - removeCallback + FileList._removeCallback ); }); - // Sets the select_all checkbox behaviour : - $('#select_all').click(function() { - if ($(this).attr('checked')) { - // Check all - $('td.filename input:checkbox').attr('checked', true); - $('td.filename input:checkbox').parent().parent().addClass('selected'); - } else { - // Uncheck all - $('td.filename input:checkbox').attr('checked', false); - $('td.filename input:checkbox').parent().parent().removeClass('selected'); - } - procesSelection(); - }); - $('.undelete').click('click', 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 = Files.getSelectedFiles('name'); - 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'); - } - params = { - files: JSON.stringify(files), - dir: FileList.getCurrentDirectory() - }; - } - - $.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.update(''); - enableActions(); - } - else { - removeCallback(result); - } - } - ); - }); - - $('.delete').click('click', function(event) { - event.preventDefault(); - var allFiles = $('#select_all').is(':checked'); - var files = []; - var params = {}; - if (allFiles) { - params = { - allfiles: true, - dir: FileList.getCurrentDirectory() - }; - } - else { - files = Files.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'); - } - } - - $.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')); - } - FileList.hideMask(); - // simply remove all files - FileList.setFiles([]); - enableActions(); - } - else { - removeCallback(result); - } - } - ); - - }); - - $('#fileList').on('click', 'td.filename input', function() { - var checkbox = $(this).parent().children('input:checkbox'); - $(checkbox).parent().parent().toggleClass('selected'); - if ($(checkbox).is(':checked')) { - var selectedCount = $('td.filename input:checkbox:checked').length; - if (selectedCount === $('td.filename input:checkbox').length) { - $('#select_all').prop('checked', true); - } - } else { - $('#select_all').prop('checked',false); - } - procesSelection(); - }); - - $('#fileList').on('click', 'td.filename a', function(event) { - var mime = $(this).parent().parent().data('mime'); - if (mime !== 'httpd/unix-directory') { - event.preventDefault(); - } - var filename = $(this).parent().parent().attr('data-file'); - var tr = FileList.findFileEl(filename); - var renaming = tr.data('renaming'); - if(!renaming){ - if(mime.substr(0, 5) === 'text/'){ //no texteditor for now - return; - } - var type = $(this).parent().parent().data('type'); - var permissions = $(this).parent().parent().data('permissions'); - var action = FileActions.getDefault(mime, type, permissions); - if(action){ - event.preventDefault(); - action(filename); - } - } - }); - /** * Override crumb URL maker (hacky!) */ diff --git a/core/js/js.js b/core/js/js.js index 325be6cdc5..27bc3c651e 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -1250,7 +1250,7 @@ function relative_modified_date(timestamp) { } /** - * @todo Write documentation + * Utility functions */ OC.Util = { // TODO: remove original functions from global namespace