Sunday 31st December, 2017

Vertical scroll fitting

Back in 2015 I introduced a plug-in for DataTables which provides the ability to dynamically alter the page length of a table to have it fit inside a container. This works well when paging is enabled and scrolling is disabled, but vertical fitting of the DataTable also works quite naturally with scrolling.

In this post I will introduce an equivalent plug-in for DataTables when scrolling is enabled - the end result is shown below:

NamePositionOfficeSalary
Tiger NixonSystem ArchitectEdinburgh$320,800
Garrett WintersAccountantTokyo$170,750
Ashton CoxJunior Technical AuthorSan Francisco$86,000
Cedric KellySenior Javascript DeveloperEdinburgh$433,060
Airi SatouAccountantTokyo$162,700
Brielle WilliamsonIntegration SpecialistNew York$372,000
Herrod ChandlerSales AssistantSan Francisco$137,500
Rhona DavidsonIntegration SpecialistTokyo$327,900
Colleen HurstJavascript DeveloperSan Francisco$205,500
Sonya FrostSoftware EngineerEdinburgh$103,600
Jena GainesOffice ManagerLondon$90,560
Quinn FlynnSupport LeadEdinburgh$342,000
Charde MarshallRegional DirectorSan Francisco$470,600
Haley KennedySenior Marketing DesignerLondon$313,500
Tatyana FitzpatrickRegional DirectorLondon$385,750
Michael SilvaMarketing DesignerLondon$198,500
Paul ByrdChief Financial Officer (CFO)New York$725,000
Gloria LittleSystems AdministratorNew York$237,500
Bradley GreerSoftware EngineerLondon$132,000
Dai RiosPersonnel LeadEdinburgh$217,500
Jenette CaldwellDevelopment LeadNew York$345,000
Yuri BerryChief Marketing Officer (CMO)New York$675,000
Caesar VancePre-Sales SupportNew York$106,450
Doris WilderSales AssistantSydney$85,600
Angelica RamosChief Executive Officer (CEO)London$1,200,000
Gavin JoyceDeveloperEdinburgh$92,575
Jennifer ChangRegional DirectorSingapore$357,650
Brenden WagnerSoftware EngineerSan Francisco$206,850
Fiona GreenChief Operating Officer (COO)San Francisco$850,000
Shou ItouRegional MarketingTokyo$163,000
Michelle HouseIntegration SpecialistSydney$95,400
Suki BurksDeveloperLondon$114,500
Prescott BartlettTechnical AuthorLondon$145,000
Gavin CortezTeam LeaderSan Francisco$235,500
Martena MccrayPost-Sales supportEdinburgh$324,050
Unity ButlerMarketing DesignerSan Francisco$85,675
Howard HatfieldOffice ManagerSan Francisco$164,500
Hope FuentesSecretarySan Francisco$109,850
Vivian HarrellFinancial ControllerSan Francisco$452,500
Timothy MooneyOffice ManagerLondon$136,200
Jackson BradshawDirectorNew York$645,750
Olivia LiangSupport EngineerSingapore$234,500
Bruno NashSoftware EngineerLondon$163,500
Sakura YamamotoSupport EngineerTokyo$139,575
Thor WaltonDeveloperNew York$98,540
Finn CamachoSupport EngineerSan Francisco$87,500
Serge BaldwinData CoordinatorSingapore$138,575
Zenaida FrankSoftware EngineerNew York$125,250
Zorita SerranoSoftware EngineerSan Francisco$115,000
Jennifer AcostaJunior Javascript DeveloperEdinburgh$75,650
Cara StevensSales AssistantNew York$145,600
Hermione ButlerRegional DirectorLondon$356,250
Lael GreerSystems AdministratorLondon$103,500
Jonas AlexanderDeveloperSan Francisco$86,500
Shad DeckerRegional DirectorEdinburgh$183,000
Michael BruceJavascript DeveloperSingapore$183,000
Donna SniderCustomer SupportNew York$112,000
Click and drag me!

Usage

Using the ScrollResize plug-in for DataTables is very simple - include the Javascript for the plug-in on your page:

JS

Then enable the feature by specifying the scrollResize option in your DataTable initialisation, along with scrollY to enable scrolling:

$('#myTable').DataTable( {
    scrollResize: true,
    scrollY: 100,
    scrollCollapse: true,
    paging: false
} );

Note that you would typically also disable paging when using ScrollResize as is done here, but that isn't mandatory (paging can be useful to improve performance on larger data sets).

Layout

On initialisation of the DataTable, the value given to scrollY (100px in the above example) is actually not important since the table's scrolling container will automatically be resized to fit the container that the table has been placed in. This is an important point since it is not the DataTable itself that defines the hight of the table, but rather the DOM container element that it is placed in.

In the example above a div element is used as a wrapper around the table and Javascript used to resize the container. There is no additional Javascript to tell DataTables that it needs to resize - this is determined automatically by the ScrollResize plug-in, keeping the initialisation simple and the script performant (see the Implementation section below for how that is done).

While you might wish to offer your end user the ability to resize the table's container, the real benefit of this approach can be seen by DataTables automatically fitting itself into a layout that you have defined:

Implementation

As with the original paging resize plug-in, for ScrollResize to automatically resize the table it needs to know when a resize has happened. We could provide and trigger a Javascript method in the DataTables API for this, but that isn't developer friendly since every update or redraw on the page would require the method to be called. It is far better to be able to determine when this needs to happen automatically.

The trouble with this is that the browser only triggers a resize event when the window size changes - not when DOM elements are moved around (since doing so would be exceptionally inefficient). The paging resize plug-in used an object to know when the container has been resized, but that caused styling issues in IE. ScrollResize uses basically the same idea, but with an iframe which does not have any such styling issues:

        var that = this;
        var obj = $('<iframe/>')
            .css( {
                position: 'absolute',
                top: 0,
                left: 0,
                height: '100%',
                width: '100%',
                zIndex: -1,
                border: 0
            } )
            .attr( 'frameBorder', '0' )
            .attr( 'src', 'about:blank' );

        obj[0].onload = function () {
            var body = this.contentDocument.body;
            var height = body.offsetHeight;
            var contentDoc = this.contentDocument;
            var defaultView = contentDoc.defaultView || contentDoc.parentWindow;

            defaultView.onresize = function () {
                // Three methods to get the iframe height, to keep all browsers happy
                var newHeight = body.clientHeight || body.offsetHeight;
                var docClientHeight = contentDoc.documentElement.clientHeight;

                if ( ! newHeight && docClientHeight ) {
                    newHeight = docClientHeight;
                }

                if ( newHeight !== height ) {
                    height = newHeight;

                    // Resize has happened - trigger callback
                }
            };
        };

        obj
            .appendTo( host )
            .attr( 'data', 'about:blank' );

With the code in place to know when the resize has happened, all we need to do is update the DataTables' scrolling container (minus the heights of the header and footer) allowing it to fit into the container perfectly.

This basic implementation of automatically knowing when a container element has been resized will almost certainly make it into DataTables 2.0 (more news on which in 2018) since it abstracts out a number of problems such as needing to call columns.adjust() when a hidden table is made visible.

Feedback welcome

As always, improvements and suggestions very welcome! The code for this plug-in is hosted on GitHub. If you have any thoughts on improvements, please send a pull request, or open a new discussion in the forums.

Happy New Year!