]> source.dussan.org Git - nextcloud-server.git/commitdiff
Breadcrumb width calculation fix
authorVincent Petry <pvince81@owncloud.com>
Fri, 23 May 2014 17:02:50 +0000 (19:02 +0200)
committerVincent Petry <pvince81@owncloud.com>
Fri, 27 Jun 2014 09:11:34 +0000 (11:11 +0200)
Rewrote the breadcrumb calculation to be more readable.

Breadcrumb now has a setMaxWidth() method to set the maximum allowed
width which is used to fit the breadcrumbs.

The breadcrumb width is now based on the container width, passed through
setMaxWidth() by the FileList class.

Now using fixed widths for the test crumbs to simulate consistent
widths across browsers which rendering engines might usually yield
different results.

apps/files/js/breadcrumb.js
apps/files/js/file-upload.js
apps/files/js/filelist.js
apps/files/tests/js/breadcrumbSpec.js
apps/files/tests/js/filelistSpec.js

index c017d710d6decad7830193b7a3d4bccc53bc4c02..7381fa8e432e5ee3e383198b2beea27777e466bf 100644 (file)
@@ -41,8 +41,9 @@
                $el: null,
                dir: null,
 
-               lastWidth: 0,
-               hiddenBreadcrumbs: 0,
+               /**
+                * Total width of all breadcrumbs
+                */
                totalWidth: 0,
                breadcrumbs: [],
                onClick: null,
                        }
 
                        this._updateTotalWidth();
-                       this.resize($(window).width(), true);
                },
 
                /**
                        return crumbs;
                },
 
+               /**
+                * Calculate the total breadcrumb width when
+                * all crumbs are expanded
+                */
                _updateTotalWidth: function () {
-                       var self = this;
-
-                       this.lastWidth = 0;
-
-                       // initialize with some extra space
-                       this.totalWidth = 64;
-                       // FIXME: this class should not know about global elements
-                       if ( $('#navigation').length ) {
-                               this.totalWidth += $('#navigation').outerWidth();
+                       this.totalWidth = 0;
+                       for (var i = 0; i < this.breadcrumbs.length; i++ ) {
+                               var $crumb = $(this.breadcrumbs[i]);
+                               $crumb.data('real-width', $crumb.width());
+                               this.totalWidth += $crumb.width();
                        }
+                       this._resize();
+               },
 
-                       if ( $('#app-navigation').length && !$('#app-navigation').hasClass('hidden')) {
-                               this.totalWidth += $('#app-navigation').outerWidth();
+               /**
+                * Show/hide breadcrumbs to fit the given width
+                */
+               setMaxWidth: function (availableWidth) {
+                       if (this.availableWidth !== availableWidth) {
+                               this.availableWidth = availableWidth;
+                               this._resize();
                        }
-                       this.hiddenBreadcrumbs = 0;
+               },
 
-                       for (var i = 0; i < this.breadcrumbs.length; i++ ) {
-                               this.totalWidth += $(this.breadcrumbs[i]).get(0).offsetWidth;
+               _resize: function() {
+                       var i, $crumb, $ellipsisCrumb;
+
+                       if (!this.availableWidth) {
+                               this.availableWidth = this.$el.width();
                        }
 
-                       $.each($('#controls .actions'), function(index, action) {
-                               self.totalWidth += $(action).outerWidth();
-                       });
+                       if (this.breadcrumbs.length <= 1) {
+                               return;
+                       }
 
-               },
+                       // reset crumbs
+                       this.$el.find('.crumb.ellipsized').remove();
 
-               /**
-                * Show/hide breadcrumbs to fit the given width
-                */
-               resize: function (width, firstRun) {
-                       var i, $crumb;
+                       // unhide all
+                       this.$el.find('.crumb.hidden').removeClass('hidden');
 
-                       if (width === this.lastWidth) {
+                       if (this.totalWidth <= this.availableWidth) {
+                               // no need to compute breadcrumbs, there is enough space
                                return;
                        }
 
-                       // window was shrinked since last time or first run ?
-                       if ((width < this.lastWidth || firstRun) && width < this.totalWidth) {
-                               if (this.hiddenBreadcrumbs === 0 && this.breadcrumbs.length > 1) {
-                                       // start by hiding the first breadcrumb after home,
-                                       // that one will have extra three dots displayed
-                                       $crumb = this.breadcrumbs[1];
-                                       this.totalWidth -= $crumb.get(0).offsetWidth;
-                                       $crumb.find('a').addClass('hidden');
-                                       $crumb.append('<span class="ellipsis">...</span>');
-                                       this.totalWidth += $crumb.get(0).offsetWidth;
-                                       this.hiddenBreadcrumbs = 2;
-                               }
-                               i = this.hiddenBreadcrumbs;
-                               // hide subsequent breadcrumbs if the space is still not enough
-                               while (width < this.totalWidth && i > 1 && i < this.breadcrumbs.length - 1) {
-                                       $crumb = this.breadcrumbs[i];
-                                       this.totalWidth -= $crumb.get(0).offsetWidth;
+                       // running width, considering the hidden crumbs
+                       var currentTotalWidth = $(this.breadcrumbs[0]).data('real-width');
+                       var firstHidden = true;
+
+                       // insert ellipsis after root part (root part is always visible)
+                       $ellipsisCrumb = $('<div class="crumb ellipsized svg"><span class="ellipsis">...</span></div>');
+                       $(this.breadcrumbs[0]).after($ellipsisCrumb);
+                       currentTotalWidth += $ellipsisCrumb.width();
+
+                       i = this.breadcrumbs.length - 1;
+
+                       // find the first section that would cause the overflow
+                       // then hide everything in front of that
+                       //
+                       // this ensures that the last crumb section stays visible
+                       // for most of the cases and is always the last one to be
+                       // hidden when the screen becomes very narrow
+                       while (i > 0) {
+                               $crumb = $(this.breadcrumbs[i]);
+                               // if the current breadcrumb would cause overflow
+                               if (!firstHidden || currentTotalWidth + $crumb.data('real-width') > this.availableWidth) {
+                                       // hide it
                                        $crumb.addClass('hidden');
-                                       this.hiddenBreadcrumbs = i;
-                                       i++;
-                               }
-                       // window is bigger than last time
-                       } else if (width > this.lastWidth && this.hiddenBreadcrumbs > 0) {
-                               i = this.hiddenBreadcrumbs;
-                               while (width > this.totalWidth && i > 0) {
-                                       if (this.hiddenBreadcrumbs === 1) {
-                                               // special handling for last one as it has the three dots
-                                               $crumb = this.breadcrumbs[1];
-                                               if ($crumb) {
-                                                       this.totalWidth -= $crumb.get(0).offsetWidth;
-                                                       $crumb.find('.ellipsis').remove();
-                                                       $crumb.find('a').removeClass('hidden');
-                                                       this.totalWidth += $crumb.get(0).offsetWidth;
-                                               }
-                                       } else {
-                                               $crumb = this.breadcrumbs[i];
-                                               $crumb.removeClass('hidden');
-                                               this.totalWidth += $crumb.get(0).offsetWidth;
-                                               if (this.totalWidth > width) {
-                                                       this.totalWidth -= $crumb.get(0).offsetWidth;
-                                                       $crumb.addClass('hidden');
-                                                       break;
-                                               }
+                                       if (firstHidden) {
+                                               // set the path of this one as title for the ellipsis
+                                               this.$el.find('.crumb.ellipsized')
+                                                       .attr('title', $crumb.attr('data-dir'))
+                                                       .tipsy();
                                        }
-                                       i--;
-                                       this.hiddenBreadcrumbs = i;
+                                       // and all the previous ones (going backwards)
+                                       firstHidden = false;
+                               } else {
+                                       // add to total width
+                                       currentTotalWidth += $crumb.data('real-width');
                                }
+                               i--;
                        }
 
-                       this.lastWidth = width;
+                       if (!OC.Util.hasSVGSupport()) {
+                               OC.Util.replaceSVG(this.$el);
+                       }
                }
        };
 
index da58e1c31b8c1690496605d7ea15390566f643a0..2637d13f9bacd88bb821721bf58e1924d534accc 100644 (file)
@@ -179,9 +179,20 @@ OC.Upload = {
                callbacks.onNoConflicts(selection);
        },
 
+       _hideProgressBar: function() {
+               $('#uploadprogresswrapper input.stop').fadeOut();
+               $('#uploadprogressbar').fadeOut(function() {
+                       $('#file_upload_start').trigger(new $.Event('resized'));
+               });
+       },
+
+       _showProgressBar: function() {
+               $('#uploadprogressbar').fadeIn();
+               $('#file_upload_start').trigger(new $.Event('resized'));
+       },
+
        init: function() {
                if ( $('#file_upload_start').exists() ) {
-
                        var file_upload_param = {
                                dropZone: $('#content'), // restrict dropZone to content div
                                autoUpload: false,
@@ -444,7 +455,7 @@ OC.Upload = {
                                        OC.Upload.log('progress handle fileuploadstart', e, data);
                                        $('#uploadprogresswrapper input.stop').show();
                                        $('#uploadprogressbar').progressbar({value: 0});
-                                       $('#uploadprogressbar').fadeIn();
+                                       OC.Upload._showProgressBar();
                                });
                                fileupload.on('fileuploadprogress', function(e, data) {
                                        OC.Upload.log('progress handle fileuploadprogress', e, data);
@@ -458,15 +469,13 @@ OC.Upload = {
                                fileupload.on('fileuploadstop', function(e, data) {
                                        OC.Upload.log('progress handle fileuploadstop', e, data);
 
-                                       $('#uploadprogresswrapper input.stop').fadeOut();
-                                       $('#uploadprogressbar').fadeOut();
+                                       OC.Upload._hideProgressBar();
                                });
                                fileupload.on('fileuploadfail', function(e, data) {
                                        OC.Upload.log('progress handle fileuploadfail', e, data);
                                        //if user pressed cancel hide upload progress bar and cancel button
                                        if (data.errorThrown === 'abort') {
-                                               $('#uploadprogresswrapper input.stop').fadeOut();
-                                               $('#uploadprogressbar').fadeOut();
+                                               OC.Upload._hideProgressBar();
                                        }
                                });
 
@@ -649,7 +658,7 @@ OC.Upload = {
                                                        //IE < 10 does not fire the necessary events for the progress bar.
                                                        if ($('html.lte9').length === 0) {
                                                                $('#uploadprogressbar').progressbar({value: 0});
-                                                               $('#uploadprogressbar').fadeIn();
+                                                               OC.Upload._showProgressBar();
                                                        }
 
                                                        var eventSource = new OC.EventSource(
@@ -668,12 +677,12 @@ OC.Upload = {
                                                        });
                                                        eventSource.listen('success', function(data) {
                                                                var file = data;
-                                                               $('#uploadprogressbar').fadeOut();
+                                                               OC.Upload._hideProgressBar();
 
                                                                FileList.add(file, {hidden: hidden, animate: true});
                                                        });
                                                        eventSource.listen('error', function(error) {
-                                                               $('#uploadprogressbar').fadeOut();
+                                                               OC.Upload._hideProgressBar();
                                                                var message = (error && error.message) || t('core', 'Error fetching URL');
                                                                OC.Notification.show(message);
                                                                //hide notification after 10 sec
index fb97b2f4595bb5eee9fa70f5ade9c78d1106e360..36f8e063d159a5b390d1e604b06c71a04b7fb68c 100644 (file)
 
                        this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this));
 
-                       $(window).resize(function() {
-                               // TODO: debounce this ?
-                               var width = $(this).width();
-                               self.breadcrumb.resize(width, false);
-                       });
+                       this._onResize = _.debounce(_.bind(this._onResize, this), 100);
+                       $(window).resize(this._onResize);
+
+                       this.$el.on('show', this._onResize);
 
                        this.$fileList.on('click','td.filename>a.name', _.bind(this._onClickFile, this));
                        this.$fileList.on('change', 'td.filename>input:checkbox', _.bind(this._onClickFileCheckbox, this));
                        }
                },
 
+               /**
+                * Event handler for when the window size changed
+                */
+               _onResize: function() {
+                       var containerWidth = this.$el.width();
+                       var actionsWidth = 0;
+                       $.each(this.$el.find('#controls .actions'), function(index, action) {
+                               actionsWidth += $(action).outerWidth();
+                       });
+
+                       // substract app navigation toggle when visible
+                       containerWidth -= $('#app-navigation-toggle').width();
+
+                       this.breadcrumb.setMaxWidth(containerWidth - actionsWidth - 10);
+               },
+
                /**
                 * Event handler for when the URL changed
                 */
                        // handle upload events
                        var fileUploadStart = this.$el.find('#file_upload_start');
 
+                       // detect the progress bar resize
+                       fileUploadStart.on('resized', this._onResize);
+
                        fileUploadStart.on('fileuploaddrop', function(e, data) {
                                OC.Upload.log('filelist handle fileuploaddrop', e, data);
 
index e3d9c757a7c0a1e01031b2ed6b28f56aefd448b4..30784fd70ad636bcf312e634b7cd4d48903c8883 100644 (file)
@@ -19,7 +19,6 @@
 *
 */
 
-/* global BreadCrumb */
 describe('OCA.Files.BreadCrumb tests', function() {
        var BreadCrumb = OCA.Files.BreadCrumb;
 
@@ -131,48 +130,42 @@ describe('OCA.Files.BreadCrumb tests', function() {
                });
        });
        describe('Resizing', function() {
-               var bc, widthStub, dummyDir,
-                       oldUpdateTotalWidth;
+               var bc, dummyDir, widths, oldUpdateTotalWidth;
 
                beforeEach(function() {
-                       dummyDir = '/short name/longer name/looooooooooooonger/even longer long long long longer long/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/last one';
+                       dummyDir = '/short name/longer name/looooooooooooonger/' +
+                               'even longer long long long longer long/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/last one';
+
+                       // using hard-coded widths (pre-measured) to avoid getting different
+                       // results on different browsers due to font engine differences
+                       widths = [41, 106, 112, 160, 257, 251, 91];
 
                        oldUpdateTotalWidth = BreadCrumb.prototype._updateTotalWidth;
                        BreadCrumb.prototype._updateTotalWidth = function() {
-                               // need to set display:block for correct offsetWidth (no CSS loaded here)
-                               $('div.crumb').css({
-                                       'display': 'block',
-                                       'float': 'left'
+                               // pre-set a width to simulate consistent measurement
+                               $('div.crumb').each(function(index){
+                                       $(this).css('width', widths[index]);
                                });
 
                                return oldUpdateTotalWidth.apply(this, arguments);
                        };
 
                        bc = new BreadCrumb();
-                       widthStub = sinon.stub($.fn, 'width');
                        // append dummy navigation and controls
                        // as they are currently used for measurements
                        $('#testArea').append(
-                               '<div id="navigation" style="width: 80px"></div>',
                                '<div id="controls"></div>'
                        );
-
-                       // make sure we know the test screen width
-                       $('#testArea').css('width', 1280);
-
-                       // use test area as we need it for measurements
                        $('#controls').append(bc.$el);
-                       $('#controls').append('<div class="actions"><div>Dummy action with a given width</div></div>');
                });
                afterEach(function() {
                        BreadCrumb.prototype._updateTotalWidth = oldUpdateTotalWidth;
-                       widthStub.restore();
                        bc = null;
                });
-               it('Hides breadcrumbs to fit window', function() {
+               it('Hides breadcrumbs to fit max allowed width', function() {
                        var $crumbs;
 
-                       widthStub.returns(500);
+                       bc.setMaxWidth(500);
                        // triggers resize implicitly
                        bc.setDirectory(dummyDir);
                        $crumbs = bc.$el.find('.crumb');
@@ -190,19 +183,23 @@ describe('OCA.Files.BreadCrumb tests', function() {
                        expect($crumbs.eq(4).hasClass('hidden')).toEqual(true);
                        expect($crumbs.eq(5).hasClass('hidden')).toEqual(true);
                        expect($crumbs.eq(6).hasClass('hidden')).toEqual(false);
+                       expect($crumbs.eq(7).hasClass('hidden')).toEqual(false);
                });
-               it('Updates ellipsis on window size increase', function() {
+               it('Updates the breadcrumbs when reducing max allowed width', function() {
                        var $crumbs;
 
-                       widthStub.returns(500);
+                       // enough space
+                       bc.setMaxWidth(1800);
+
+                       expect(bc.$el.find('.ellipsis').length).toEqual(0);
+
                        // triggers resize implicitly
                        bc.setDirectory(dummyDir);
-                       $crumbs = bc.$el.find('.crumb');
 
                        // simulate increase
-                       $('#testArea').css('width', 1800);
-                       bc.resize(1800);
+                       bc.setMaxWidth(950);
 
+                       $crumbs = bc.$el.find('.crumb');
                        // first one is always visible
                        expect($crumbs.eq(0).hasClass('hidden')).toEqual(false);
                        // second one has ellipsis
@@ -213,37 +210,35 @@ describe('OCA.Files.BreadCrumb tests', function() {
                        // subsequent elements are hidden
                        expect($crumbs.eq(2).hasClass('hidden')).toEqual(true);
                        expect($crumbs.eq(3).hasClass('hidden')).toEqual(true);
-                       expect($crumbs.eq(4).hasClass('hidden')).toEqual(true);
                        // the rest is visible
+                       expect($crumbs.eq(4).hasClass('hidden')).toEqual(false);
                        expect($crumbs.eq(5).hasClass('hidden')).toEqual(false);
                        expect($crumbs.eq(6).hasClass('hidden')).toEqual(false);
                });
-               it('Updates ellipsis on window size decrease', function() {
+               it('Removes the ellipsis when there is enough space', function() {
                        var $crumbs;
 
-                       $('#testArea').css('width', 2000);
-                       widthStub.returns(2000);
+                       bc.setMaxWidth(500);
                        // triggers resize implicitly
                        bc.setDirectory(dummyDir);
                        $crumbs = bc.$el.find('.crumb');
 
-                       // simulate decrease
-                       bc.resize(500);
-                       $('#testArea').css('width', 500);
+                       // ellipsis
+                       expect(bc.$el.find('.ellipsis').length).toEqual(1);
 
-                       // first one is always visible
+                       // simulate increase
+                       bc.setMaxWidth(1800);
+
+                       // no ellipsis
+                       expect(bc.$el.find('.ellipsis').length).toEqual(0);
+
+                       // all are visible
                        expect($crumbs.eq(0).hasClass('hidden')).toEqual(false);
-                       // second one has ellipsis
                        expect($crumbs.eq(1).hasClass('hidden')).toEqual(false);
-                       expect($crumbs.eq(1).find('.ellipsis').length).toEqual(1);
-                       // there is only one ellipsis in total
-                       expect($crumbs.find('.ellipsis').length).toEqual(1);
-                       // subsequent elements are hidden
-                       expect($crumbs.eq(2).hasClass('hidden')).toEqual(true);
-                       expect($crumbs.eq(3).hasClass('hidden')).toEqual(true);
-                       expect($crumbs.eq(4).hasClass('hidden')).toEqual(true);
-                       // the rest is visible
-                       expect($crumbs.eq(5).hasClass('hidden')).toEqual(true);
+                       expect($crumbs.eq(2).hasClass('hidden')).toEqual(false);
+                       expect($crumbs.eq(3).hasClass('hidden')).toEqual(false);
+                       expect($crumbs.eq(4).hasClass('hidden')).toEqual(false);
+                       expect($crumbs.eq(5).hasClass('hidden')).toEqual(false);
                        expect($crumbs.eq(6).hasClass('hidden')).toEqual(false);
                });
        });
index 743ebf9706ac55ee5f3e902c5a53ebb772196c2d..2bcc43cf2adb10f99272677dbdb9e3a4157e0503 100644 (file)
@@ -21,6 +21,7 @@
 
 describe('OCA.Files.FileList tests', function() {
        var testFiles, alertStub, notificationStub, fileList;
+       var bcResizeStub;
 
        /**
         * Generate test file data
@@ -52,6 +53,9 @@ describe('OCA.Files.FileList tests', function() {
        beforeEach(function() {
                alertStub = sinon.stub(OC.dialogs, 'alert');
                notificationStub = sinon.stub(OC.Notification, 'show');
+               // prevent resize algo to mess up breadcrumb order while
+               // testing
+               bcResizeStub = sinon.stub(OCA.Files.BreadCrumb.prototype, '_resize');
 
                // init parameters and test table elements
                $('#testArea').append(
@@ -125,6 +129,7 @@ describe('OCA.Files.FileList tests', function() {
 
                notificationStub.restore();
                alertStub.restore();
+               bcResizeStub.restore();
        });
        describe('Getters', function() {
                it('Returns the current directory', function() {