Can SlickGrid's row height be dynamically altered?

Dave Clausen picture Dave Clausen · May 10, 2012 · Viewed 16.2k times · Source

We are implementing a user preference to (instantly) show "more" or "less" data on the grid. "More" should increase the row-height (with every row having the same increased height).

When the user toggles, we update our DataView, and call setOptions on the grid with an updated rowHeight value. We then call invalidate() and render().

But row height isn't being updated. :(

Could someone advise a solution? Should we alter the height directly via CSS? If so, any tips on doing this?

Answer

violet313 picture violet313 · Apr 1, 2015

Indeed it is possible to dynamically update row height based on user interaction. the Slickgrid API provides all that we need.

Because:

  1. we can add/remove rows dynamically;
  2. we can dynamically apply custom css at row & cell level.


Here is a simple demo to get things started:

////////////////////////////////////////////////////////////////////////////////
//example codez re trying to create a grid with rows of dynamic height to
//cater for folks that wanna bung loads of stuff in a field & see it all...
//by [email protected] ~ visit: www.violet313.org/slickgrids
//have all the fun with it  ;) vxx.
////////////////////////////////////////////////////////////////////////////////
modSlickgridSimple=(
function()
{
    var _dataView=null;
    var _grid=null;
    var _data=[];


    //////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////
    var getPaddingItem=function(parent , offset)
    {
        var item={};

        for (var prop in _data[0]) item[prop]=null;
        item.id=parent.id+"."+offset;

        //additional hidden padding metadata fields
        item._collapsed=     true;
        item._isPadding=     true;

        return item;
    }

    //////////////////////////////////////////////////////////////
    //this just builds our expand collapse button
    //////////////////////////////////////////////////////////////
    var onRenderIDCell=function(row, cell, value, columnDef, item)
    {
        if (item._isPadding==true); //render nothing
        else if (item._collapsed) return "<div class='toggle expand'></div>";
        else
        {
            var html=[];
            var rowHeight=_grid.getOptions().rowHeight;

            //V313HAX:
            //putting in an extra closing div after the closing toggle div and ommiting a
            //final closing div for the detail ctr div causes the slickgrid renderer to
            //insert our detail div as a new column ;) ~since it wraps whatever we provide
            //in a generic div column container. so our detail becomes a child directly of
            //the row not the cell. nice =)  ~no need to apply a css change to the parent
            //slick-cell to escape the cell overflow clipping.

            //sneaky extra </div> inserted here-----------------v
            html.push("<div class='toggle collapse'></div></div>");

            html.push("<div class='dynamic-cell-detail' ");   //apply custom css to detail
            html.push("style='height:", item._height, "px;"); //set total height of padding
            html.push("top:", rowHeight, "px'>");             //shift detail below 1st row
            html.push("<div>",item._detailContent,"</div>");  //sub ctr for custom styling
            //&omit a final closing detail container </div> that would come next

            return html.join("");
        }
    }

    //////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////
    var onRowClick=function(e, args)
    {
        _dataView.beginUpdate();

        if ($(e.target).hasClass("toggle"))
        {
            var item=_dataView.getItem(args.row);

            if (item)
            {
                if (!item._collapsed)
                {
                    item._collapsed=true;
                    for (var idx=1; idx<=item._sizePadding; idx++)
                        _dataView.deleteItem(item.id+"."+idx);
                    item._sizePadding=0;
                }
                else
                {
                    item._collapsed=false;
                    kookupDynamicContent(item);
                    var idxParent=_dataView.getIdxById(item.id);
                    for (var idx=1; idx<=item._sizePadding; idx++)
                        _dataView.insertItem(idxParent+idx, getPaddingItem(item,idx));
                }
                _dataView.updateItem(item.id, item);
            }
            e.stopImmediatePropagation();
        }

        _dataView.endUpdate();
    }

    //////////////////////////////////////////////////////////////
    var gridOptions={ enableColumnReorder:  true };

    //////////////////////////////////////////////////////////////
    var _gridColumns=
    [
        {
            id:         "id",
            name:       "",
            field:      "id",
            resizable:  false,
            width:      20,
            formatter:  onRenderIDCell,
        },
        {id: "title",        name: "Title",         field: "title",        resizable: true},
        {id: "duration",     name: "Duration",      field: "duration",     resizable: true},
        {id: "pcComplete",   name: "% Complete",    field: "pcComplete",   resizable: true},
        {id: "start",        name: "Start",         field: "start",        resizable: true},
        {id: "finish",       name: "Finish",        field: "finish",       resizable: true},
        {id: "effortDriven", name: "Effort Driven", field: "effortDriven", resizable: true},
    ];

    //////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////
    var kookupTestData=(function()
    {
        for (var i = 0; i < 100; i++)
            _data[i] =
            {
                id:               i,
                title:            "Task " + i,
                duration:         "5 days",
                pcComplete:       Math.round(Math.random() * 100),
                start:            "01/01/2009",
                finish:           "01/05/2009",
                effortDriven:     (i % 5 == 0),

                //additional hidden metadata fields
                _collapsed:       true,
                _sizePadding:     0,     //the required number of pading rows
                _height:          0,     //the actual height in pixels of the detail field
                _isPadding:       false,
            };
    })();

    //////////////////////////////////////////////////////////////
    //create the detail ctr node. this belongs to the dev & can be custom-styled as per
    //////////////////////////////////////////////////////////////
    var kookupDynamicContent=function(item)
    {
        //add some random oooks as fake detail content
        var oookContent=[];
        var oookCount=Math.round(Math.random() * 12)+1;
        for (var next=0; next<oookCount; next++)
            oookContent.push("<div><span>oook</span></div>");
        item._detailContent=oookContent.join("");

        //calculate padding requirements based on detail-content..
        //ie. worst-case: create an invisible dom node now &find it's height.
        var lineHeight=13; //we know cuz we wrote the custom css innit ;)
        item._sizePadding=Math.ceil((oookCount*lineHeight) / _grid.getOptions().rowHeight);
        item._height=(item._sizePadding * _grid.getOptions().rowHeight);
    }

    //////////////////////////////////////////////////////////////
    //jquery onDocumentLoad
    //////////////////////////////////////////////////////////////
    $(function()
    {
        //initialise the data-model
        _dataView=new Slick.Data.DataView();
        _dataView.beginUpdate();
        _dataView.setItems(_data);
        _dataView.endUpdate();

        //initialise the grid
        _grid=new Slick.Grid("#grid-simple", _dataView, _gridColumns);
        _grid.onClick.subscribe(onRowClick);

        //wire up model events to drive the grid per DataView requirements
        _dataView.onRowCountChanged.subscribe
            (function(){ _grid.updateRowCount();_grid.render(); });

        _dataView.onRowsChanged.subscribe
            (function(e, a){ _grid.invalidateRows(a.rows);_grid.render(); });

        $(window).resize(function() {_grid.resizeCanvas()});
    });
}
)();
//////////////////////////////////////////////////////////////
//done ;)
::-webkit-scrollbar       
{ 
    width:              12px; 
    background-color:   #B9BACC; 
}
::-webkit-scrollbar-track 
{ 
    color:              #fff; 
    -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 
    border-radius:      10px;  
}
::-webkit-scrollbar-thumb 
{ 
    color:              #96A9BB; 
    border-radius:      10px;  
    -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5); 
}

body
{
    font-family:        Arial, Helvetica, sans-serif;
    background-color:   #131313;
    position:           absolute;
    top:                5px;
    bottom:             5px;
    left:               5px;
    right:              5px;
}

#grid-simple
{
    position:         absolute;
    top:              0px;
    left:             0px;
    right:            0px;    
    bottom:           0px;
    margin:           auto;
    font-size:        12px;
    background-color: #ECEEE9;
}

.toggle
{
    height:           16px;
    width:            16px;
    display:          inline-block;
}
.toggle.expand
{
    background: url(https://violet313.github.io/assets/expand.gif) no-repeat center center;
}

.toggle.collapse
{
    background: url(https://violet313.github.io/assets/collapse.gif) no-repeat center center;
}


/*--- generic slickgrid padding pollyfill  ----------------------*/
 
.dynamic-cell-detail
{
    z-index:            10000;
    position:           absolute;
    background-color:   #F4DFFA;
    margin:             0;
    padding:            0;
    width:              100%;
    display:            table;
}

.dynamic-cell-detail > :first-child
{
    display:            table-cell;
    vertical-align:     middle;
    text-align:         center;
    font-size:          12px;
    line-height:        13px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="http://code.jquery.com/ui/1.9.2/jquery-ui.js"></script>
<script type="text/javascript" src="https://cdn.rawgit.com/Celebio/SlickGrid/master/lib/jquery.event.drag-2.2.js"></script>
<script type="text/javascript" src="https://cdn.rawgit.com/Celebio/SlickGrid/master/slick.core.js"></script>
<script type="text/javascript" src="https://cdn.rawgit.com/Celebio/SlickGrid/master/slick.grid.js"></script>
<script type="text/javascript" src="https://cdn.rawgit.com/Celebio/SlickGrid/master/slick.dataview.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.rawgit.com/Celebio/SlickGrid/master/slick.grid.css">
<link rel="stylesheet" type="text/css" href="https://cdn.rawgit.com/Celebio/SlickGrid/master/slick-default-theme.css">
<link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/themes/base/jquery-ui.css">


<div id="grid-simple"></div>

Less than 200 lines of codes.
fiddle with it!

Incidentally, this is the kind of approach that the also excellent Datatables provides (almost) natively through it's API. &imo it is the correct pattern; & how i am choosing to implement my own stuff using Slickgrid. But it involves a slight hack and in any case does *not exactly meet the OP requirements; which i claim is possible.


To do dynamic row-heights per-cell, we employ a similar trick but we must also deal with a few side-effects:~

styling away the row divisions

We must:

  1. escape the per-cell overflow clipping
  2. remove unwanted row stipeyness
  3. remove row borders

The Slickgrid API provides row-based styling via the Grid.getItemMetadata callback interface. In the next fiddle, on line 107, see the onRenderRow implementation of this interface:
fiddle with it!

Notice also on lines 148-150, i invoke the Slickgrid Grid.setCellCssStyles API to add a custom dynamic-cell css class that sets the overflow to visible in order to style away the per-cell overflow clipping.

column resizing

If the detail content is static, column resizing is a gimme.

Detail content that responds to a change in column width (flowing text or wotnot) requires some work. Padding rows need to be dynamically added and removed accordingly. See (from line 66) the addPadding and trimPadding functions in the next fiddle:
fiddle with it!

sorting

There's some work to do here also. we need to ensure that no matter whether we are sorting up or down, the padding remains contiguously underneath the parent. See the comparer on line 136 in this next fiddle:
fiddle with it!

filtering

Pretty much a one-liner: if it's padding, then delegate the comparison to the parent. job done. See the pcFilter on line 192 in the next fiddle:
fiddle with it!

Yay! that's resizing, sorting & filtering in under 500 lines of fairly legible, liberally commented custom javascripts.. i have in fact seen certain fancy input-range-slider pollyfills with more lines of code ;)
acu


Caveats

I have only covered the basics. There is that whole selectable/editable aspect to Slickgrid,, ~which goes beyond my current requirements (sry).
Also:

  • example-code only; not production ready. you have been warned etc etc =)
  • examples seem to work in most modern browsers; i have /not/ tried with iE; versions >= 11 might be ok..

Further infos

There is more to be said about this than can reasonably be squeeezed into a SO answer -notwithstanding the no-references policyguidelines. Anyone interested in knowing more about all this stuff can go here where i go into a fair bit more detail.

One final example

Here's a final fun example. it employs all of the above functionality but as can be seen, i have ditched the expando-rows and there are two dynamic content fields. Also, fyi, this example makes use of MutationObservers in order to generate onPostRender events as an alternative to the Slickgrid native asyncPostRender column option callback:
fiddle with it!

And there we have it. -some of the way towards a DataView-like Slickgrid extension-mod; & all without needing to resort to horrid hax on the lovely Slickgrid codes. yippee ;)

OoOok! This post is some years old; and i see now that there are several forks of the currently unmaintained original project. ie: https://github.com/6pac/SlickGrid