diff options
Diffstat (limited to 'apps/files')
-rw-r--r-- | apps/files/js/filelist.js | 172 | ||||
-rw-r--r-- | apps/files/tests/js/filelistSpec.js | 84 |
2 files changed, 150 insertions, 106 deletions
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 02754d7acbb..53bb3a5c868 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -31,6 +31,20 @@ window.FileList = { totalPages: 0, /** + * Compare two file info objects, sorting by + * folders first, then by name. + */ + _fileInfoCompare: 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); + }, + + /** * Initialize the file list and its components */ initialize: function() { @@ -42,6 +56,7 @@ window.FileList = { // TODO: FileList should not know about global elements this.$el = $('#filestable'); this.$fileList = $('#fileList'); + this.files = []; this.fileSummary = this._createSummary(); @@ -321,7 +336,8 @@ window.FileList = { index = this.pageNumber * this.pageSize; while (count > 0 && index < this.files.length) { - tr = this.add(this.files[index], {updateSummary: false}); + tr = this._renderRow(this.files[index], {updateSummary: false}); + this.$fileList.append(tr); if (selected) { tr.addClass('selected'); tr.find('input:checkbox').prop('checked', true); @@ -497,8 +513,10 @@ window.FileList = { tr.append(td); return tr; }, + /** - * Adds an entry to the files table using the data from the given file data + * Adds an entry to the files array and also into the DOM + * * @param fileData map of file attributes * @param options map of attributes: * - "insert" true to insert in a sorted manner, false to append (default) @@ -506,6 +524,47 @@ window.FileList = { * @return new tr element (not appended to the table) */ add: function(fileData, options) { + var index = -1; + var $tr = this._renderRow(fileData, options); + options = options || {}; + + this.isEmpty = false; + + if (options.insert) { + index = this._findInsertionIndex(fileData); + if (index < this.files.length) { + this.files.splice(index, 0, fileData); + this.$fileList.children().eq(index).before($tr); + } + else { + this.files.push(fileData); + this.$fileList.append($tr); + } + } + else { + this.files.push(fileData); + this.$fileList.append($tr); + } + + // 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, @@ -524,16 +583,6 @@ window.FileList = { ); var filenameTd = tr.find('td.filename'); - // sorted insert is expensive, so needs to be explicitly - // requested - if (options.insert) { - this.insertElement(fileData.name, type, tr); - } - else { - this.$fileList.append(tr); - } - FileList.isEmpty = false; - // TODO: move dragging to FileActions ? // enable drag only for deletable files if (permissions & OC.PERMISSION_DELETE) { @@ -569,12 +618,6 @@ window.FileList = { filenameTd.css('background-image', 'url(' + previewUrl + ')'); } } - - // defaults to true if not defined - if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) { - this.fileSummary.add(fileData, true); - this.updateEmptyContent(); - } return tr; }, /** @@ -742,9 +785,10 @@ window.FileList = { * "updateSummary": true to update the summary (default), false otherwise * @return deleted element */ - remove:function(name, options){ + remove: function(name, options){ options = options || {}; var fileEl = FileList.findFileEl(name); + var index = fileEl.index(); if (!fileEl.length) { return null; } @@ -752,51 +796,32 @@ window.FileList = { // 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.$fileList.find('tr').length; + 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); } return fileEl; }, - insertElement:function(name, type, element) { - // find the correct spot to insert the file or folder - var pos, - fileElements = this.$fileList.find('tr[data-file][data-type="'+type+'"]:not(.hidden)'); - if (name.localeCompare($(fileElements[0]).attr('data-file')) < 0) { - pos = -1; - } else if (name.localeCompare($(fileElements[fileElements.length-1]).attr('data-file')) > 0) { - pos = fileElements.length - 1; - } else { - for(pos = 0; pos<fileElements.length-1; pos++) { - if (name.localeCompare($(fileElements[pos]).attr('data-file')) > 0 - && name.localeCompare($(fileElements[pos+1]).attr('data-file')) < 0) - { - break; - } - } - } - if (fileElements.exists()) { - if (pos === -1) { - $(fileElements[0]).before(element); - } else { - $(fileElements[pos]).after(element); - } - } else if (type === 'dir' && !FileList.isEmpty) { - this.$fileList.find('tr[data-file]:first').before(element); - } else if (type === 'file' && !FileList.isEmpty) { - this.$fileList.find('tr[data-file]:last').before(element); - } else { - this.$fileList.append(element); + /** + * 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._fileInfoCompare(fileData, this.files[index]) > 0) { + index++; } - FileList.isEmpty = false; - FileList.updateEmptyContent(); + return index; }, 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); @@ -844,51 +869,18 @@ window.FileList = { file: oldname }, success: function(result) { + var fileInfo; if (!result || result.status === 'error') { OC.dialogs.alert(result.data.message, t('core', 'Could not rename file')); - // revert changes - newname = oldname; - tr.attr('data-file', newname); - var path = td.children('a.name').attr('href'); - 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('.'))); - } - tr.find('.fileactions').effect('highlight', {}, 5000); - tr.effect('highlight', {}, 5000); - // remove loading mark and recover old image - td.css('background-image', oldBackgroundImage); + fileInfo = oldFileInfo; } else { - var fileInfo = result.data; - tr.attr('data-mime', fileInfo.mime); - tr.attr('data-etag', fileInfo.etag); - if (fileInfo.isPreviewAvailable) { - Files.lazyLoadPreview(directory + '/' + fileInfo.name, result.data.mime, function(previewpath) { - tr.find('td.filename').attr('style','background-image:url('+previewpath+')'); - }, null, null, result.data.etag); - } - else { - tr.find('td.filename') - .removeClass('preview') - .attr('style','background-image:url(' - + OC.Util.replaceSVGIcon(fileInfo.icon) - + ')'); - } + fileInfo = result.data; } // reinsert row - tr.detach(); - FileList.insertElement( tr.attr('data-file'), tr.attr('data-type'),tr ); - // update file actions in case the extension changed - FileActions.display( tr.find('td.filename'), true); + FileList.files.splice(tr.index(), 1); + tr.remove(); + FileList.add(fileInfo, {insert: true}); } }); } diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index 93e7c81cb1f..1b155f4f1df 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -233,6 +233,7 @@ describe('FileList tests', function() { expect($tr.find('.filesize').text()).toEqual('0 B'); }); it('adds new file to the end of the list', function() { + var $tr; var fileData = { type: 'file', name: 'P comes after O.txt' @@ -241,15 +242,55 @@ describe('FileList tests', function() { $tr = FileList.add(fileData); expect($tr.index()).toEqual(4); }); - it('adds new file at correct position in insert mode', function() { + 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], {insert: true}); + } + 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; var fileData = { type: 'file', name: 'P comes after O.txt' }; - FileList.setFiles(testFiles); + for (var i = 0; i < testFiles.length; i++) { + FileList.add(testFiles[i], {insert: true}); + } $tr = FileList.add(fileData, {insert: true}); // after "One.txt" + expect($tr.index()).toEqual(2); + expect(FileList.files[2]).toEqual(fileData); + }); + it('inserts new folder at correct position in insert mode', function() { + var $tr; + var fileData = { + type: 'dir', + name: 'somedir2 comes after somedir' + }; + for (var i = 0; i < testFiles.length; i++) { + FileList.add(testFiles[i], {insert: true}); + } + $tr = FileList.add(fileData, {insert: true}); expect($tr.index()).toEqual(1); + expect(FileList.files[1]).toEqual(fileData); + }); + it('inserts new file at the end correctly', function() { + var $tr; + var fileData = { + type: 'file', + name: 'zzz.txt' + }; + for (var i = 0; i < testFiles.length; i++) { + FileList.add(testFiles[i], {insert: true}); + } + $tr = FileList.add(fileData, {insert: true}); + expect($tr.index()).toEqual(4); + expect(FileList.files[4]).toEqual(fileData); }); it('removes empty content message and shows summary when adding first file', function() { var fileData = { @@ -280,6 +321,7 @@ describe('FileList tests', function() { 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); $summary = $('#filestable .summary'); @@ -294,6 +336,7 @@ describe('FileList tests', function() { 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); $summary = $('#filestable .summary'); @@ -358,6 +401,7 @@ describe('FileList tests', function() { $summary = $('#filestable .summary'); expect($summary.hasClass('hidden')).toEqual(true); 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); }); @@ -383,37 +427,41 @@ describe('FileList tests', function() { function doRename() { var $input, request; - FileList.setFiles(testFiles); + for (var i = 0; i < testFiles.length; i++) { + FileList.add(testFiles[i], {insert: true}); + } // trigger rename prompt FileList.rename('One.txt'); $input = FileList.$fileList.find('input.filename'); - $input.val('One_renamed.txt').blur(); + $input.val('Tu_after_three.txt').blur(); expect(fakeServer.requests.length).toEqual(1); 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'}); + 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('One_renamed.txt').length).toEqual(1); + expect(FileList.findFileEl('Tu_after_three.txt').length).toEqual(1); // input is gone expect(FileList.$fileList.find('input.filename').length).toEqual(0); } - it('Keeps renamed file entry if rename ajax call suceeded', function() { + it('Inserts renamed file entry at correct position if rename ajax call suceeded', function() { doRename(); fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({ status: 'success', data: { - name: 'One_renamed.txt' + name: 'Tu_after_three.txt', + type: 'file' } })); // element stays renamed expect(FileList.findFileEl('One.txt').length).toEqual(0); - expect(FileList.findFileEl('One_renamed.txt').length).toEqual(1); + 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); }); @@ -429,7 +477,8 @@ describe('FileList tests', function() { // element was reverted expect(FileList.findFileEl('One.txt').length).toEqual(1); - expect(FileList.findFileEl('One_renamed.txt').length).toEqual(0); + 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); }); @@ -440,12 +489,12 @@ describe('FileList tests', function() { fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({ status: 'success', data: { - name: 'One_renamed.txt' + name: 'Tu_after_three.txt' } })); - $tr = FileList.findFileEl('One_renamed.txt'); - expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=One_renamed.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! xit('Correctly updates file link after rename when path has same name', function() { @@ -457,20 +506,23 @@ describe('FileList tests', function() { fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({ status: 'success', data: { - name: 'One_renamed.txt' + name: 'Tu_after_three.txt' } })); - $tr = FileList.findFileEl('One_renamed.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('List rendering', function() { it('renders a list of files using add()', function() { var addSpy = sinon.spy(FileList, 'add'); + expect(FileList.files.length).toEqual(0); + expect(FileList.files).toEqual([]); FileList.setFiles(testFiles); - expect(addSpy.callCount).toEqual(4); expect($('#fileList tr').length).toEqual(4); + expect(FileList.files.length).toEqual(4); + expect(FileList.files).toEqual(testFiles); addSpy.restore(); }); it('updates summary using the file sizes', function() { |