SUPPORT / SAMPLES & SAS NOTES
 

Support

Usage Note 24066: In ODS HTML, how can I freeze my table headers while I scroll the page?

DetailsAboutRate It

This FAQ provides numerous examples, organized into three sections:

DATA Step Examples

Below are two DATA step examples that use the banner and frame method. The banner is a header that stays in a fixed position. There is no option to do this, so use the DATA step along with ODS. The file specified in the banner is created with the BODY= option by ODS HTML. View output.

   /* example 1 */

   data one;
     set sashelp.class sashelp.class;
   run;


   proc template;
     define style styles.test;
       parent=styles.default;
       style body from document /
             topmargin=0;
       style frame from document /
             /* This makes the TOC 0 Percentage of the frame */
             /* The TOC can be dragged to view               */
             contentsize=0;
     end;
   run;

   ods html body='temp.html'
       contents='temp1.html'
       frame='frametest.html' style=styles.test;

   proc print data=one;
     title;
   run;

   ods html close;

   data _null_;
     infile 'frametest.html' lrecl=100 flowover pad sharebuffers;
     input line $char100.;
     file "frametest1.html" noprint;
       if _n_=11 then do;
          put '<FRAMESET ROWS="55,*">' ;
          put '<FRAME NAME="banner" src="temp.html">';
       end;
     put _infile_;
   run;

   dm "wbrowse 'frametest1.html'";


   /* example 2: The entire frame file can be written out  */
   /* by using the DATA step.                              */

   data one;
      set sashelp.class sashelp.class;
   run;

   proc template;
     define style styles.test;
       parent=styles.default;
       style body from document /
             topmargin=0;
     end;
   run;

   ods html body='body.html'
       contents='contents.html'
       frame='frame.html' style=styles.test;

   proc print data=one;
   run;

   ods html close;

   data _null_;
   file 'frame.html';

   put '<HTML>';
   put '<HEAD>';
   put '<META http-equiv="Content-type" content="text/html">';
   put '<TITLE>SAS Output Frame</TITLE>' ;
   put '</HEAD>'  ;
   put '<FRAMESET ROWS="55,*">' ;
   put '<FRAME NAME="banner" src="body.html">' ;
   put '<FRAMESET FRAMEBORDER=auto FRAMESPACING=3 COLS="0,*">'  ;
   put '<FRAME MARGINWIDTH="4" MARGINHEIGHT="0" SRC="contents.html"
       NAME="contents" SCROLLING=auto>' ;
   put '<FRAME MARGINWIDTH="9" MARGINHEIGHT="0" SRC="body.html"
       NAME="body" SCROLLING=auto>' ;
   put '</FRAMESET>' ;
   put '</HTML>' ;

   run;

   dm "wbrowse 'frame.html'";
 

ODS MARKUP Examples

If you do not want to create a frame as in the previous two examples, you can try the next two examples, which use the ODS MARKUP language to freeze the column headers and prevent them from being scrolled. The headers and columns are separated and have no knowledge of one another. To get the headers and columns to line up correctly, you must add a width to the headers and the column values with the CELLWIDTH= attribute. By default, the header and cell values are the length of the largest value. Below is example code. View output.

   ods path(prepend) work.template(update);
   proc template;
     define tagset tagsets.test;
     parent=tagsets.htmlcss;
     define event doc;
     start:
       put "<html>" NL;
       PUT "<HEAD>" nl;
       PUT '<style type="text/css">' nl;
       PUT "<!--" nl;
       PUT "#header {position:relative;left:0;top:-15;z-index:1;width:}" nl;
       PUT "#body   {position:relative;left:0;top:-15;z-index:0;}" nl;
       PUT "//-->" nl;
       PUT "</style>" nl;
       PUT "<script>" nl;
       PUT "<!--" nl;
       PUT "  function skrollaa()" nl;
       PUT "{" nl;
       PUT 'document.all["header"].style.top =
            document.body.scrollTop - 15;' nl;
       PUT "}" nl;
       PUT "//-->" nl;
       PUT "</script>" nl;
       PUT "<HEAD>" nl;
       PUT '<body onscroll="skrollaa()">' nl;
     finish:
       put "</html>" NL;
     end;
     define event colspecs;
       put "<p>" NL ;
     end;
     define event table_head;
     start:
       PUT "<DIV ID='header' align='center'>" nl;
       PUT "<TABLE rule=all frame=box> " nl;
       put "<thead>" NL;
     finish:
       put "</thead>" NL;
       PUT "</TABLE>" nl;
       PUT "</DIV>" nl;
     end;
     define event table_body;
     start:
       PUT "<DIV ID='body' Align='center'>" nl;
       PUT "<TABLE rules=all frame=box>" nl;
       put "<tbody>" NL;
     finish:
       put "<tbody>" NL;
       PUT "</TABLE>" nl;
       PUT "</DIV>" nl;
     end;
     define event table;
     finish:
       put "</table></p>" NL;
     end;
     define event data;
       put "<td";
       trigger pre_post;
       putq " class=" HTMLCLASS;
       trigger align;
       trigger style_inline;
       trigger rowcol;
       putq ' width=' cellwidth;
       put ">";
       put "<pre>" / if exist( asis );
       put VALUE;
     finish:
       trigger pre_post;
       put "</pre>" / if exist( asis );
       put "</td>" NL;
     end;
     end;
   run;

   data one;
     set sashelp.class sashelp.class;
   run;

   ods listing close;
   ods markup file='xxx.html'
       tagset=tagsets.test stylesheet='s24.css';

   proc print data=one noobs
        style(column)=[cellwidth=50] style(header)={cellwidth=50};
     var name age sex height weight;
   run;

   ods markup close;

The next MARKUP example also creates a way to prevent the headers from becoming scrollable. This example adds a scroll bar on to the table itself. So if the table goes past the scrollable screen, then the scroll bar is added to the table. As with the prior example, the header and body of the table are created separately and have no knowledge of each other, so they may not line up correctly. To help with this issue, see the code comments for tips, or go to the next example. View output.

/****************************************************************************/
/* The headers and the data are in two separate tables so we have to        */
/* do a few things to make sure that they align. In the event table         */
/* and the event table_body we need to add a fixed width for the table      */
/* so that the tables will be the same size. The style CSS style property   */
/* table-layout:fixed is specified so that it honors the table widths       */
/* specified.                                                               */
/*                                                                          */
/* If this does not line up, then the only thing that you will need to      */
/* change is the margin-right style attribute in the table event. This      */
/* will tell the header how much it needs to back up or go forward to line  */
/* up with the body. You should be able to get this to line up exactly. I   */
/* gave it a hard width of 415 which is you will need to adjust this        */
/* according to your output. The table width in the event table and         */
/* table_body would need to be at least the width of the all the            */
/* columns.                                                                 */
/*                                                                          */
/* The height:550 CSS style property in the table_body event determines     */
/* how long the table should be initially before the scroll bar is added.   */
/* The overflow property is added with the default value of auto. Therefore,*/
/* a scroll bar is added when the table goes beyond the height specified.   */
/*                                                                          */
/****************************************************************************/

   ods path(prepend) work.template(update);

   proc template;
     define tagset tagsets.test;
     parent=tagsets.htmlcss;
     define event table;
     start:
       put "<div align=center>" ;
       put "<table width=415 style=""table-layout:fixed;margin-right:11""";
       putq " border=" BORDERWIDTH;
       put " border=""1""" / if !exist( BORDERWIDTH );
       putq " cellspacing=" CELLSPACING;
       putq " cellpadding=" CELLPADDING;
       putq " rules=" RULES;
       putq " frame=" FRAME;
       trigger align;
       put ">" NL;
     finish:
       break;
     end;
     define event colgroup;
       break;
     finish:
       break;
     end;
     define event colspec_entry;
       break;
     end;
     define event table_head;
       put "<thead>" NL;
     finish:
       put "</thead>" NL;
       put "</table>" NL;
       put "</div>" NL;;
     end;
     define event table_body;
       put "<div align=center style='height:550;overflow:auto;'>" NL;
       put "<table width=415 frame=box >" Nl;
       put "<tbody>" NL;
     finish:
       put "<tbody>" NL;
       put "</table>";
       put "</div>";
       put "</tr>";
       put "</table>";
     end;
     define event data;
       put "<td";
       trigger pre_post;
       putq " class=" HTMLCLASS;
       trigger align;
       trigger style_inline;
       trigger rowcol;
       put ' width=' cellwidth;
       put ">";
       put "<pre>" / if exist( asis );
       put VALUE;
     finish:
       trigger pre_post;
       put "</pre>" / if exist( asis );
       put "</td>" NL;
     end;
     end;
   run;
   data one;
     set sashelp.class sashelp.class;
   run;
   ods markup
       path="c:\temp"(url=none)
       file="temp.html"
       tagset=tagsets.test
       stylesheet='temp.css';

   proc report data=one nowd style(column header)={cellwidth=50};
   run;

   ods markup close;

Cascading Style Sheet (CSS) Examples

The below example uses a behavior to produce the frozen headers. Using this method, you do not have to worry about aligning the table. The behavior needs two parameters that you must specify. These are the BodyHeight= tag, which you add with the TAGATTR= attribute, and the HTMLCLASS= attribute which imports the class selector .tblMain that you add with the HEADTEXT= option. The BodyHeight= tag, which is added to the table tag, determines the height of the table before the scroll bar is added.

If the table does not reach the height specified, the scroll bar is not added. The Scroll2.htc file, which is defined with the .tblMain class, should be stored somewhere locally where it can be referenced. Below is an example of the code and the output it produces. If you are running SAS 9.1, the easiest way to implement this is to use the HTML3 destination. View output. You can also download a copy of the .htc file.

  /* SAS 8.2 */

   ods html
     file="temp.html"
     headtext="<style> .tblMain {behavior:url(c:\scroll2.htc)}</style>";

   proc report
        data=sashelp.class
        style(report)={tagattr=" bodyHeight=""500""" HTMLCLASS="tblMain"}
        nowd;
   run;

   proc print
        data=sashelp.class
        style(table)={tagattr=" bodyHeight=""500""" htmlclass="tblMain"};
   run;

   ods html close;

  /* SAS 9.1 */

   ods html3
       file="temp.html"
       headtext="<style> .tblMain {behavior:url (c:\scroll2.htc)}</style>";

   proc report
        data=sashelp.class
        style(report)={tagattr=" bodyHeight=""500"" class=""tblMain"" "}
        nowd;
   run;

   proc print
        data=sashelp.class
        style(table)={tagattr=" bodyHeight=""500"" htmlclass=""tblMain"" "};
   run;

   ods html3 close;

This can also be used with PROC TEMPLATE for procedures that do not support the STYLE= option.

   proc template;
     define style styles.test;
     parent=styles.default;
       style table from table /
             tagattr=" bodyHeight=""500"" class=""tblMain"" "
             htmlstyle="behavior:url(c:\scroll2.htc)";
     end;
   run;

   ods html file="c:\temp.html" style=styles.test;

   proc print data=sashelp.class;
   run;

   ods html close;

The below example also enables you to create frozen column headers without worrying about the the headers lining up with the table. This example uses PROC TEMPLATE with CSS style properties. The Overflow CSS style property adds the scroll bars to the table when the parameters specified for the height and width are reached. The ScrollTop CSS style property specifies where the headers are frozen. View output.

/* To change the size of the table, modify the width: and the Height: CSS */
/* style properties. Currently, they are set at a width of 40% and height */
/* 348px.                                                                 */

   proc template;
     define style styles.test;
     parent=styles.default;
       style table from table /
             outputwidth=99%
             prehtml='<DIV
                     style="overflow:auto;
                     WIDTH:40%;HEIGHT:348px"
                     id="data">'
             posthtml="</div>";
       style header from header /
             htmlstyle='z-index:20;
                       POSITION:relative;
                       TOP:
                       expression(document.getElementById("data").scrollTop-2)';

     end;
   run;
   ods html file="temp.html" style=styles.test;

   proc print data=sashelp.shoes noobs;
   run;

   ods html close;

With a few modifications to the above code, you can freeze both the column and row headers. This method relies on the Scrolleft property to determine the left-most edge to freeze. For the columns that you want to freeze, add the style attribute HTMLCLASS with the concatenated CSS style properties Header and RowHeader. This enables you to use both style properties. This works fine for a small amount of data. If your table is large, then take a look at the additional code. Depending on the size of the table, this still could be a little slow. See Largescroll.sas and largescroll1.sas. View output.

   proc template;
     define style styles.test;
     parent=styles.default;
       style table from table /
             outputwidth=99%
             prehtml='<DIV
                     style="overflow:auto;
                     WIDTH:30%;HEIGHT:348px"
                     id="data">'
             posthtml="</div>";
       style header from header /
             htmlstyle='z-index:20;
                       POSITION:relative;
                       TOP:
                       expression(document.getElementById("data").scrollTop-2)';
       style rowheader from rowheader /
             htmlstyle='position:relative;
                       left:
                       expression(document.getElementById("data").scrollLeft)';
       end;
   run;
   ods html file="temp.html" style=styles.test;

   proc print data=sashelp.class;
     id name age /
        style(header)={
             htmlclass="rowheader header"
             htmlstyle="z-index:30"};
   run;

   ods html close;

The below example creates a data grid that also sorts the column when clicking on the column header. View output.

   proc template;
     define style styles.test;
     parent=styles.default;
       style table from table /
             htmlstyle="behavior:url(c:\tablesort91test3.htc)"
                       outputwidth=99%
                       prehtml='<DIV
                       style="overflow:auto;
                       WIDTH:40%;HEIGHT:348px"
                       id="data">'
             posthtml="</div>";
       style header from header /
             htmlstyle='z-index:20;POSITION:relative;
                       TOP:
                       expression(document.getElementById("data").scrollTop-2)';
       style rowheader from rowheader /
             htmlstyle='position:relative;
                       left:
                       expression(document.getElementById("data").scrollLeft)';
     end;
   run;

   ods html file="temp.html" style=styles.test ;

   proc print data=sashelp.class noobs;
     id name age /
        style(header)={
             htmlclass="rowheader header"
             htmlstyle="z-index:30"};
     var _numeric_ / style(header)={tagattr="type='Number'"};

   run;

   ods html close;

Another option is the tableEditor tagset. This tagset has functionality to include freezing column and rowheaders, sorting, filtering and exporting data. See the tableEditor tagset.

See also the full SAS Notes and Concepts for ODS.



Operating System and Release Information

Product FamilyProductSystemSAS Release
ReportedFixed*
SAS SystemBase SASAlln/a
* For software releases that are not yet generally available, the Fixed Release is the software release in which the problem is planned to be fixed.