diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index 03ebdccb32..963fc64782 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -606,7 +606,7 @@ OC.Upload = { {dir:$('#dir').val(), filename:name}, function(result) { if (result.status === 'success') { - FileList.add(result.data, {hidden: hidden, insert: true}); + FileList.add(result.data, {hidden: hidden, animate: true}); } else { OC.dialogs.alert(result.data.message, t('core', 'Could not create file')); } @@ -619,7 +619,7 @@ OC.Upload = { {dir:$('#dir').val(), foldername:name}, function(result) { if (result.status === 'success') { - FileList.add(result.data, {hidden: hidden, insert: true}); + FileList.add(result.data, {hidden: hidden, animate: true}); } else { OC.dialogs.alert(result.data.message, t('core', 'Could not create folder')); } @@ -657,7 +657,7 @@ OC.Upload = { var file = data; $('#uploadprogressbar').fadeOut(); - FileList.add(file, {hidden: hidden, insert: true}); + FileList.add(file, {hidden: hidden, animate: true}); }); eventSource.listen('error',function(error) { $('#uploadprogressbar').fadeOut(); diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 53bb3a5c86..3bf5b2d967 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -26,8 +26,6 @@ window.FileList = { // number of files per page pageSize: 20, - // zero based page number - pageNumber: 0, totalPages: 0, /** @@ -227,9 +225,6 @@ window.FileList = { }, _onScroll: function(e) { - if (this.pageNumber + 1 >= this.totalPages) { - return; - } if ($(window).scrollTop() + $(window).height() > $(document).height() - 500) { this._nextPage(true); } @@ -324,16 +319,17 @@ window.FileList = { * @param animate true to animate the new elements */ _nextPage: function(animate) { - var tr, index, count = this.pageSize, + var index = this.$fileList.children().length, + count = this.pageSize, + tr, newTrs = [], selected = this.isAllSelected(); - if (this.pageNumber + 1 >= this.totalPages) { + if (index >= this.files.length) { return; } this.pageNumber++; - index = this.pageNumber * this.pageSize; while (count > 0 && index < this.files.length) { tr = this._renderRow(this.files[index], {updateSummary: false}); @@ -343,7 +339,7 @@ window.FileList = { tr.find('input:checkbox').prop('checked', true); } if (animate) { - tr.addClass('appear transparent'); // TODO + tr.addClass('appear transparent'); newTrs.push(tr); } index++; @@ -365,7 +361,7 @@ window.FileList = { * This operation will rerender the list and update the summary. * @param filesArray array of file data (map) */ - setFiles:function(filesArray) { + setFiles: function(filesArray) { // detach to make adding multiple rows faster this.files = filesArray; this.pageNumber = -1; @@ -516,34 +512,57 @@ window.FileList = { /** * 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: - * - "insert" true to insert in a sorted manner, false to append (default) * - "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 = this._renderRow(fileData, options); + var $tr; + var $rows; + var $insertionPoint; options = options || {}; - this.isEmpty = false; + // 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 - 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); + $rows = this.$fileList.children(); + index = this._findInsertionIndex(fileData); + if (index > this.files.length) { + index = this.files.length; + } + else { + $insertionPoint = $rows.eq(index); + } + + // 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); } } - else { - this.files.push(fileData); - this.$fileList.append($tr); + + this.isEmpty = false; + this.files.splice(index, 0, fileData); + + if ($tr && options.animate) { + $tr.addClass('appear transparent'); + window.setTimeout(function() { + $tr.removeClass('transparent'); + }); } // defaults to true if not defined @@ -880,7 +899,7 @@ window.FileList = { // reinsert row FileList.files.splice(tr.index(), 1); tr.remove(); - FileList.add(fileInfo, {insert: true}); + FileList.add(fileInfo); } }); } @@ -1351,7 +1370,7 @@ $(document).ready(function() { FileList.remove(file.name); // create new file context - data.context = FileList.add(file, {insert: true}); + data.context = FileList.add(file, {animate: true}); } } }); diff --git a/apps/files/tests/js/fileactionsSpec.js b/apps/files/tests/js/fileactionsSpec.js index 3c22c84b86..f5eafba509 100644 --- a/apps/files/tests/js/fileactionsSpec.js +++ b/apps/files/tests/js/fileactionsSpec.js @@ -30,6 +30,7 @@ describe('FileActions tests', function() { $body.append(''); // dummy files table $filesTable = $body.append('
'); + FileList.files = []; }); afterEach(function() { $('#dir, #permissions, #filestable').remove(); diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index 1b155f4f1d..7316cb7531 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -236,7 +236,7 @@ describe('FileList tests', function() { var $tr; var fileData = { type: 'file', - name: 'P comes after O.txt' + name: 'ZZZ.txt' }; FileList.setFiles(testFiles); $tr = FileList.add(fileData); @@ -245,7 +245,7 @@ describe('FileList tests', 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}); + FileList.add(testFiles[i]); } expect(FileList.files[0].name).toEqual('somedir'); expect(FileList.files[1].name).toEqual('One.txt'); @@ -259,9 +259,9 @@ describe('FileList tests', function() { name: 'P comes after O.txt' }; for (var i = 0; i < testFiles.length; i++) { - FileList.add(testFiles[i], {insert: true}); + FileList.add(testFiles[i]); } - $tr = FileList.add(fileData, {insert: true}); + $tr = FileList.add(fileData); // after "One.txt" expect($tr.index()).toEqual(2); expect(FileList.files[2]).toEqual(fileData); @@ -273,9 +273,9 @@ describe('FileList tests', function() { name: 'somedir2 comes after somedir' }; for (var i = 0; i < testFiles.length; i++) { - FileList.add(testFiles[i], {insert: true}); + FileList.add(testFiles[i]); } - $tr = FileList.add(fileData, {insert: true}); + $tr = FileList.add(fileData); expect($tr.index()).toEqual(1); expect(FileList.files[1]).toEqual(fileData); }); @@ -286,9 +286,9 @@ describe('FileList tests', function() { name: 'zzz.txt' }; for (var i = 0; i < testFiles.length; i++) { - FileList.add(testFiles[i], {insert: true}); + FileList.add(testFiles[i]); } - $tr = FileList.add(fileData, {insert: true}); + $tr = FileList.add(fileData); expect($tr.index()).toEqual(4); expect(FileList.files[4]).toEqual(fileData); }); @@ -428,7 +428,7 @@ describe('FileList tests', function() { var $input, request; for (var i = 0; i < testFiles.length; i++) { - FileList.add(testFiles[i], {insert: true}); + FileList.add(testFiles[i]); } // trigger rename prompt @@ -516,14 +516,12 @@ describe('FileList tests', function() { }); 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($('#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() { var $summary; @@ -593,6 +591,117 @@ describe('FileList tests', function() { expect($summary.find('.info').text()).toEqual('0 folders and 1 file'); }); }); + describe('Rendering next page on scroll', function() { + + function generateFiles(startIndex, endIndex) { + var files = []; + var name; + for (var i = startIndex; i <= endIndex; i++) { + name = 'File with index '; + if (i < 10) { + // do not rely on localeCompare here + // and make the sorting predictable + // cross-browser + name += '0'; + } + name += i + '.txt'; + files.push({ + id: i, + type: 'file', + name: name, + mimetype: 'text/plain', + size: i * 2, + etag: 'abc' + }); + } + return files; + } + + beforeEach(function() { + FileList.setFiles(generateFiles(0, 64)); + }); + it('renders only the first page', function() { + 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); + expect($('#fileList tr').length).toEqual(40); + FileList._nextPage(true); + expect($('#fileList tr').length).toEqual(60); + FileList._nextPage(true); + expect($('#fileList tr').length).toEqual(65); + 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({ + 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); + }); + it('does not inserts into the DOM if insertion point is not the visible page ', function() { + 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 tr').length).toEqual(40); + 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({ + id: 2000, + type: 'file', + name: 'File with index 19b.txt' + }); + expect($('#fileList tr').length).toEqual(21); + 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); + expect($('#fileList tr').length).toEqual(40); + FileList._nextPage(true); + expect($('#fileList tr').length).toEqual(60); + FileList._nextPage(true); + expect($('#fileList tr').length).toEqual(65); + FileList._nextPage(true); + FileList.add({ + id: 2000, + type: 'file', + name: 'File with index 88.txt' + }); + expect($('#fileList tr').length).toEqual(66); + 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]); + } + expect($('#fileList tr').length).toEqual(20); + FileList._nextPage(true); + expect($('#fileList tr').length).toEqual(40); + FileList._nextPage(true); + expect($('#fileList tr').length).toEqual(60); + FileList._nextPage(true); + expect($('#fileList tr').length).toEqual(80); + FileList._nextPage(true); + expect($('#fileList tr').length).toEqual(81); + FileList._nextPage(true); + expect($('#fileList tr').length).toEqual(81); + }); + }); describe('file previews', function() { var previewLoadStub;