11

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?

Dave Clausen
  • 1,302
  • 1
  • 12
  • 23

3 Answers3

17

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 violet313@gmail.com ~ 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 code.
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 ;)
<br>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; and all without needing to resort to horrid hacks on the lovely Slickgrid code.

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

starball
  • 20,030
  • 7
  • 43
  • 238
violet313
  • 1,912
  • 1
  • 15
  • 19
  • Hi @violet313 I am trying your solution above but finding it difficult.. http://violet313.org/slickgrids doesn't appear to be up any more.. tried getting it through some internet archive but no luck.. could you post this info anywhere else or is it gone forever? Thanks! – Shorttylad Mar 02 '20 at 16:00
  • 1
    @Shorttylad, yes. sry it's been on my todo to fix the link :/ i'm currently without a webhost but maybe i can check the whole _howto_ into github at least :) -can you bear with for a few days,, there's always the jsfiddles; they still work.. – violet313 Mar 03 '20 at 18:45
  • 1
    Thank you - appreciate it.. I have been using the fiddles as a frame of reference and trying to implement it into my own grid but the new row will not show. I probably don't have a good enough understanding of how it works though so the additional info be a great help. – Shorttylad Mar 04 '20 at 13:56
  • 1
    @Shorttylad. ok, done. [that link where i go into more detail](https://violet313.github.io/) under **Further Infos** should be good again. fyi, the `rawgit cdn` is about to die so now i'm pulling in the slickgrid javascripts from https://6pac.github.io/SlickGrid/ -if there's ever any breaking changes. someone let me know and i'll serve it from my github pages i suppose.. sighs – violet313 Mar 05 '20 at 17:19
  • 1
    Wow.. that is alot of detail.. no excuses now :). I think it may be the fact I am using grouping on my grid which is causing me issues but I will give it another attempt! Thanks – Shorttylad Mar 05 '20 at 22:55
  • 1
    lol, i feel it's the very least i can do. i owe plenty to those who have put a whole lot more effort into being helpful than i ever have. haha. best of luck with the javascripts @Shorttylad ! – violet313 Mar 06 '20 at 12:43
7

You can do this via css. Take a look at the slick.grid.css file, and make your desired changes there.

Take a look at the

.slick-row.ui-widget-content, .slick-row.ui-state-active classes

OR

You can use the rowHeight property of the SlickGrid

take a look at the grid options

https://github.com/mleibman/SlickGrid/wiki/Grid-Options

ballade4op52
  • 2,142
  • 5
  • 27
  • 42
G-Man
  • 7,232
  • 18
  • 72
  • 100
  • Thanks GX for your response. Note that I'm looking to _dynamically_ update row height based on user interaction ... altering the rowHeight property does not seem to accomplish this. When I can, I'll try the CSS route - if this works, I'll mark this as the accepted answer. Thanks again! – Dave Clausen May 21 '12 at 19:39
  • Thanks for asking @epotter ... I actually haven't had the chance (or prioritization) to revisit this. Yet I'll go ahead and mark GX's response as accepted. – Dave Clausen Jun 01 '12 at 22:14
  • 14
    If you haven't tried it, why mark it as completed? I have the same question about row height, but now I don't know if this answer is useful. – bradley.ayers Oct 25 '12 at 04:22
  • I tried using the rowHeight but it calculates in px by default..I cannot make it autoIncrease..I trying changing line 909 (slickGrid.js) to auto. Let me know if anyone got it working – Viral Feb 22 '13 at 01:28
  • Due to public outcry, I've unchecked this as the accepted-answer. It seemed like the acceptable answer; but I had refactored so that I didn't need dynamic alteration anymore. Also, I was more of a SO newbie back then. Apologies. – Dave Clausen Feb 19 '16 at 16:03
2

I attached a function to my grid which enables me to expand the row height:

var normalHeight = 25;
var expandedHeight = 100;
var columns = [
    {id: "col1", name: "Column 1", field: "col1", expanded: false},
    {id: "col2", name: "Column 2", field: "col2"}
];
var options = {
    rowHeight: normalHeight
};
var dataView = new Slick.Data.DataView();
var grid = new Slick.Grid(element, dataView, columns, options);
grid.updateOptions = function(expanded){
    var columns = grid.getColumns();
    if(!expanded){
        options['rowHeight'] = normalHeight;
        columns[0]['expanded'] = false;
    }else{
        options['rowHeight'] = expandedHeight;
        columns[0]['expanded'] = true;
    }
    grid.setOptions(options);
    grid.setColumns(columns);
    grid.invalidate();
    grid.render();
}

It seems to work nicely, hopefully this is helpful to someone.

JstnPwll
  • 8,585
  • 2
  • 33
  • 56
  • It's been a long time since I used this so I'm not 100% sure, but I think I had a checkbox to toggle whether media was expanded or collapsed. I called this function whenever the checkbox changed. – JstnPwll Jun 20 '17 at 14:45