3

let's say have 20 columns total.

I want to fix till 5 columns. (means only important ID, etc will be fixed while doing horizonal scrolling)

How can I achieve it in Visual Force Datatable ?

1 Answer 1

1

You need to basically duplicate the table and populate with the first 5 columns (if you want them to be fixed in your case) and then show/hide the table with CSS/Javascript when the horizontal scroll reaches the beginning of the original table.

Let's say this is your original table (I've copied code from one of my previous solutions):

<apex:pageBlockTable id="table" headerClass="headerRow" first="" value="{!tempReservations}" var="tmp" width="100%" columns="26">
    <apex:facet name="caption" >
        <apex:outputText value="{!opportunityName}"/>
    </apex:facet>
    <apex:column id="no" headerValue="#" headerClass="alignCenter">
        <apex:outputText rendered="{!tmp.index != 0}" value="{!tmp.index}." />
    </apex:column>
    <apex:column id="centreName" headerValue="Centre Name" style="font-weight: bold; white-space: nowrap;">
        <apex:outputField value="{!tmp.product.Westfield_Center_Location__c}" />
    </apex:column>
    <apex:column id="productName" headerValue="Product Name" style="font-weight: bold; white-space: nowrap;">
        <!-- MINI PAGE LAYOUT -->
        <apex:outputText rendered="{!tmp.product.Id != null}">
            <a target="_blank" id="{!tmp.product.Id}" onmouseover="LookupHoverDetail.getHover('{!tmp.product.Id}', '/{!tmp.product.Id}/m?retURL={!tmp.product.Id}&isAjaxRequest=1').show();" onmouseout="LookupHoverDetail.getHover('{!tmp.product.Id}').hide();" onfocus="LookupHoverDetail.getHover('{!tmp.product.Id}', '/{!tmp.product.Id}/m?retURL={!tmp.product.Id}&isAjaxRequest=1').show();" onblur="LookupHoverDetail.getHover('{!tmp.product.Id}').hide();" href="/{!tmp.product.Id}">{!tmp.product.Name}</a>
        </apex:outputText>
    </apex:column>
    <apex:column id="startDate" headerValue="Start Date" styleClass="{!IF(tmp.index == 0, 'markWhite', '')}" style="white-space: nowrap;">
        <apex:outputField value="{!tmp.reservation.Start_Date__c}" style="width: 80px;" />
    </apex:column>
    <apex:column id="endDate" headerValue="End Date" styleClass="{!IF(tmp.index == 0, 'markWhite', '')}" style="white-space: nowrap;">
        <apex:outputField value="{!tmp.reservation.End_Date__c}" style="width: 80px;" />
    </apex:column>
    <apex:column id="resStatus" headerValue="Reservation Status" style="white-space: nowrap;">
        <apex:outputField value="{!tmp.reservation.Reservation_Status__c}" />
    </apex:column>
    <apex:column id="exclusive" headerValue="Exclusive" style="text-align: center; white-space: nowrap;" headerClass="alignCenter">
        <apex:outputField value="{!tmp.reservation.Exclusive__c}" rendered="{!tmp.index != 0}" />
    </apex:column>
    <apex:column id="medStatus" headerValue="Media Status" style="white-space: nowrap;">
        <apex:outputField value="{!tmp.reservation.Media_Status__c}" />
    </apex:column>
    <apex:column id="discount" headerValue="Discount %" style="text-align: center;" headerClass="alignCenter">
        <apex:outputField value="{!tmp.reservation.Discounted_Amount__c}" style="white-space: nowrap;" />
    </apex:column>
</apex:pageBlockTable>

What you need to do next is - create another temporary table with the desired columns like this :

<apex:pageBlockTable id="tableFixed" headerClass="headerRow" first="" value="{!tempReservations}" var="tmp" columns="3" style="position: absolute; border: none; display: none;">
    <apex:column id="no" headerValue="#" headerClass="alignCenter" style="height: 25px; white-space: nowrap;">
        <apex:outputText rendered="{!tmp.index != 0}" value="{!tmp.index}." style="height: 25px;" />
    </apex:column>
    <apex:column id="centreName" headerValue="Centre Name" style="font-weight: bold; white-space: nowrap; height: 25px;">
        <apex:outputField value="{!tmp.product.Westfield_Center_Location__c}" />
    </apex:column>
    <apex:column id="productName" headerValue="Product Name" style="font-weight: bold; border-right: 2px solid black; white-space: nowrap;">
        <!-- MINI PAGE LAYOUT -->
        <apex:outputText rendered="{!tmp.product.Id != null}">
            <a target="_blank" id="{!tmp.product.Id}" onmouseover="LookupHoverDetail.getHover('{!tmp.product.Id}', '/{!tmp.product.Id}/m?retURL={!tmp.product.Id}&isAjaxRequest=1').show();" onmouseout="LookupHoverDetail.getHover('{!tmp.product.Id}').hide();" onfocus="LookupHoverDetail.getHover('{!tmp.product.Id}', '/{!tmp.product.Id}/m?retURL={!tmp.product.Id}&isAjaxRequest=1').show();" onblur="LookupHoverDetail.getHover('{!tmp.product.Id}').hide();" href="/{!tmp.product.Id}">{!tmp.product.Name}</a>
        </apex:outputText>
    </apex:column>
</apex:pageBlockTable>

For positioning purposes add the following javascript code just after your tables. As per the comments there are some cross-browser issues which I couldn't figure out back in the day, so play around with the pixels until you get the right positioning.

<script type="text/javascript">
    function getAbsY(elt) { return (elt.y) ? elt.y : getAbsPos(elt,"Top"); }
    function getAbsPos(elt,which) 
    {
        iPos = 0;
        while (elt != null) 
        {
            iPos += elt["offset" + which];
            elt = elt.offsetParent;
        }
        return iPos;
    }

    var fixedTable = document.getElementById('{!$Component.tableFixed}');
    var actualTable = document.getElementById('{!$Component.table}');
    var fixedTableWidth = fixedTable.style.width = actualTable.rows[0].cells[0].clientWidth + actualTable.rows[0].cells[1].clientWidth + actualTable.rows[0].cells[2].clientWidth + 'px';

    fixedTable.style.minWidth = fixedTable.clientWidth + 'px';
    fixedTable.style.borderRight = '2px solid black';

    // TODO this cries to be refactored
    if('MozBoxSizing' in document.documentElement.style)
    {
        // firefox adds extra 30px from somewhere... 
        fixedTable.style.top = (getAbsY(actualTable) - document.getElementById('AppBodyHeader').clientHeight) + 'px';
    }
    else if(!!(window.opera && window.opera.version))
    {
        // opera adds only 3px extra, again not sure where from (SF maybe has some issues)
        fixedTable.style.top = (getAbsY(actualTable) - document.getElementById('AppBodyHeader').clientHeight + 27) + 'px';

        // opera doesn't care about height, even if you explicitly specify, it ignores it. Maybe SF has some weird overrides, no time for that now
        var fixedTableCells = fixedTable.getElementsByTagName('td');
        for(var i = 0; i < fixedTableCells.length; i++)
        {
            fixedTableCells[i].style.height = '26px';
        }
    }
    else
    {
        // safari, chrome
        fixedTable.style.top = (getAbsY(actualTable) - document.getElementById('AppBodyHeader').clientHeight + 30) + 'px';

        // chrome and safari are adding 1 extra pixel for the cells height, not sure why
        var fixedTableCells = fixedTable.getElementsByTagName('td');
        for(var i = 0; i < fixedTableCells.length; i++)
        {
            fixedTableCells[i].style.height = '26px';
        }
    }
</script>

And finally, you want to attach onscoll event on the window so that every time the user scrolls, the code executes and determines whether to show or hide the fixed table. Add this code just before your form or page tag.

<script type="text/javascript">
    // Disable calendar auto popup on page load
    function setFocusOnLoad() {}

    var startOffset = 0;
    window.onscroll = fixTable;

    function fixTable(event)
    {
        var actualTableOffset = GetRealOffset('{!$Component.table}');
        var pageOffset = getScrollLeft();
        var tablePosition = fixedTable.style.position;

        if(actualTableOffset > 0 && pageOffset > actualTableOffset)
        {
            startOffset = actualTableOffset;
            fixedTable.style.display = 'block';
            fixedTable.style.left = (pageOffset - 10) + 'px'; // -10 for the padding/margin
        }
        else if(pageOffset < startOffset || actualTableOffset == 0)
        {
            fixedTable.style.display = 'none';
        }
    }

    function GetRealOffset(id)
    {
        var elem = document.getElementById(id);
        var leftOffset = elem.offsetLeft;
        var parent = elem.offsetParent;

        if(parent != null)
        {
            while(parent != document.body) 
            {
                leftOffset += parent.offsetLeft;
                parent = parent.offsetParent;
            }
        }
        return leftOffset;
    }

    function getScrollLeft()
    {
        if(typeof pageXOffset != 'undefined')
        {
            //most browsers
            return pageXOffset;
        }
        else
        {
            var B = document.body; //IE 'quirks'
            var D = document.documentElement; //IE with doctype
            D = (D.clientWidth) ? D : B;
            return D.scrollLeft;
        }
    }
</script>

Hope this helps and you should be able to work it out. This is the easiest concept I've come up with so far in regards to fixed tables for scrolling. Comments and suggestions for a better solution are welcomed

3
  • as there will be vertical scrolling, how can fixed columns matched with vertical scrolling?
    – kitokid
    Aug 16, 2013 at 5:31
  • The question was specifically for horizontal scrolling. It's the same concept for vertical scrolling, though you need to calculate the vertical scroll and offset, and move the table by the Y axis. Aug 16, 2013 at 5:37
  • Oh and also you will need another table having only 1 row, the titles of the original table, and you need to move that one by the Y axis, instead of the one used in the example. Aug 16, 2013 at 5:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.