<% /*
     File:          shared/resultset_table_v2.inc

     $Revision$
     $Id$

   */
%>

<!-- #include file="resultset_filter_table.inc" -->
<!-- #include file="resultset_flex.inc" -->
<!-- #include file="iface.inc" -->
<!-- #include file="../api/api.inc" -->
<%
FCLTHeader.Requires({ plugins: ["jQuery"],
                      js: ["resultset_table_v2.js", "sorttable.js"]
                    });
var APIname = getQParam("API","");
var useHamburger = S("inline_actions_hamburger");
if (getQParam("touch","") == 1 || device.test(device.isTouch))
    useHamburger = 1; // Hamburger onClick
var hasInlineActions = false;
var book_id = null;
if (APIname == "REPORT")
{
    var API = new API_func();
    book_id = getQParam("BOOKMARK");
    var sql = "SELECT fac_bookmark_path, "
            + "       fac_bookmark_query"
            + "  FROM fac_bookmark"
            + " WHERE fac_bookmark_id = " + safe.quoted_sql(book_id);
    var oRs = Oracle.Execute(sql);
    if (oRs.EOF)
    {
        Response.Write("Bookmark niet gevonden");
        Response.End;
    }
    var path = oRs("fac_bookmark_path").Value;
    var query = oRs("fac_bookmark_query").Value;
    oRs.Close();

    if (rooturl + path != Request.ServerVariables("SCRIPT_NAME"))
    {
        shared.internal_error("Invalid bookmark path");
    }

    //
    // getQParam/getFParam onderscheppen en 'voeden' uit FAC_BOOKMARK_URL
    //
    // Simuleer een Request.Querystring compatible collection
    function bmcoll(key)
    {
        var nn = 1;
        var coll = Server.CreateObject("Scripting.Dictionary");
        var params = query.split("&");
        for (var i in params)
        {
            var xx = params[i].split("=");
            if (xx[0].toUpperCase() == key.toUpperCase() && xx.length > 1)
            {
                coll.Add(nn++, unescape(String(xx[1]).replace(/\+/g, " "))); // spaties gaan niet vanzelf
            }
        }
        return coll;
    }
    getQParam = function (pName, defVal)
    {
        return _get_Param(bmcoll, pName, defVal)
    }
    getQParamInt = function (pName, defVal)
    {
        return _get_ParamInt(bmcoll, pName, defVal)
    }
    getQParamDate = function (pName, defVal)
    {
        return _get_ParamDate(bmcoll, pName, defVal)
    }
}

var p_autosortby = getQParam('autosortby', "") || getFParam('autosortby', "");
var autosortbyrev = false;
if (p_autosortby.substr(0,1) == '-')
{
    var autosortbyrev = true;
    p_autosortby = p_autosortby.substr(1);
}

// <form> printform is nodig bij printen reserveringen i.v.m. POST formulier
// maar <form> mag niet binnen een ander <form> voorkomen en moet eigenlijk wel binnen body staan
// Formulier als tekstring samenstellen en m.b.v. $(document).ready(function(){}) binnen de body zetten (resultset_table_v2.js)
var postformHTML = '';

if (Request.ServerVariables("REQUEST_METHOD") == "POST")
{
   var postformHTML = '<div id=\"postform\">'
                    + '<form id=\"printform\" name=\"printform\" target=\"_blank\" method=\"post\">'
                    + '<input type=\"hidden\" id=\"outputmode\" name=\"outputmode\" value=\"0\">' // input outputmode wordt gezet in doOutput()
                    + '<input type=\"hidden\" id=\"autosortby\" name=\"autosortby\" value=\"0\">' // input autosortby wordt gezet in doOutput()
                    + '<input type=\"hidden\" id=\"showall\" name=\"showall\" value=\"1\">';
   var items = new Enumerator(Request.Form());

  while (!items.atEnd())
  {
    var itemName = items.item();
    for (var i = 1; i <= Request.Form(itemName).count; i++)
    { // De input velden outputmode en showall zijn standaard al gedefinieerd. Niet dubbel definiëren
      // Een eventuele submit ook niet doorgeven tijdens afdruk.
      if (itemName != "outputmode" && itemName != "showall" && itemName != "do_submit")
        postformHTML += '<input type=\"hidden\" name=\"' + safe.htmlattr(itemName)
                     + '\" value=\"' + safe.htmlattr(Request.Form(itemName)(i)) + '\">';
    }
    items.moveNext();
  }
  postformHTML += '</form>'
                + '</div>';
}

var isPlain = false;
var isPrinting = false;
var isExcel = false;
var isCSV = false;

function ResultsetTable(params)
{

  this.tableClassName = "rstable";
  this.rowClass = null;
  this.rowData = null;
  this.newHeader = null;
  this.emptySetString = L("lcl_empty_rstable");
  this.sql = null;
  this.dataset = null;
  this.filter = "";
  this.filterParams = null;
  this.ID = null;
  this.flexModule = null;
  this.flexId = null;
  this.flexGroupId = null;
  this.flexParentGroupId = null;
  this.flexChangeNiveau = null;
  this.flexParams = null;
  this.rowNum = 0;
  this.keyColumn = null;
  this.dblClick = null;
  this.rowActionEnabler = null;
  this.inlineDetails = null;
  this.hasInlineDetails = null;
  this.summaryCalc = null;
  this.summaryShow = null;
  this.totalCalc = null;
  this.totalShow = null;
  this.summaryId = null;
  this.noPrint = null;   /* if true: suppress print/excel icons */
  this.noExcel = null;   /* if true: suppress excel icon */
  this.outputmode = null; // print naar: 0 = screen, 1 = printer met flex, 2 = excel met flex, 3 = XML/XSL, 4 = CSV,
                          //             5 = printer zonder flex, 6 = excel zonder flex.
  this.endText = null;
  this.showAll = null;
  this.showProperties = null;
  this.propertiesColumn = null; // alleen nog maar in gebruik voor FIN flexkenmerken detailregels
  this.addurl = null;
  this.suppressKeyRepeat = null;
  this.inline = null;

  // multiple resultsets
  this.rstableCount = 1;
  this.rstableNr = 1;

  this.buildHistogram = false; // over *alle* kolommen van de sql! Originele waarden!
  this.Counter = {};

  this.noSetSummary = false;
  var param;
  // Neem alle meegegeven parameters mee.
  for (param in params)
      this[param] = params[param];

  isPlain = ((this.outputmode || 0) == 0);
  isPrinting = (this.outputmode == 1 || this.outputmode == 5);
  isExcel = ((this.outputmode == 2 || this.outputmode == 6) && S("excel_mode")==0);
  isXMLXSL = (this.outputmode == 3);
  isCSV = ((this.outputmode == 4) || (((this.outputmode == 2 || this.outputmode == 6) && S("excel_mode")==1)));
  this.printFlex = (this.outputmode == 1 || this.outputmode == 2 || this.showProperties) && this.flexModule != null && this.flexId != null;

  if (isPrinting || isExcel)
  {
    this.inlineDetails = null;
    this.hasInlineDetails = null;
  }

  this.columns = new Array();
  this.actions = new Array();
  if (this.inline || isExcel)
  {
    this.noPrint = true; // Geen printer fuctionaliteit
    this.noSetSummary = true; // Geen summary zetten in de titelbalk
    this.noLoading = true; // Geen loading
  }
}

ResultsetTable.prototype.processResultset = __rsProcessResultset;
ResultsetTable.prototype.startTable = __rsStartTable;
ResultsetTable.prototype.makeTableHeader = __rsMakeTableHeader;
ResultsetTable.prototype.makeTableRow = __rsMakeTableRow;
ResultsetTable.prototype.makeTableFootnote = __rsMakeTableFootnote;
ResultsetTable.prototype.endTable = __rsEndTable;
ResultsetTable.prototype.addColumn = __rsAddColumn;
ResultsetTable.prototype.startPrint = __rsStartPrint;
ResultsetTable.prototype.endPrint = __rsEndPrint;
ResultsetTable.prototype.addAction = __rsAddAction;

function __rsAddAction(params)
{
    /* Nu ook inline actions bij PDA (hamburger)

    if (device.test(device.isTouch) && !params.isDefault) {
        params.single = false; // touch heeft geen mouseover. Dan alleen de default onclick
    }

    */

    this.actions.push(params);
};

// JGL: O.a. UWVA smoelenboek
ResultsetTable.prototype.ProcessAsXMLXSL = function()
{
    var xmlDoc = new ActiveXObject("MSXML2.DOMDocument.6.0");

    xmlDoc.appendChild(xmlDoc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"windows-1252\""))
    // MGE: De encoding komt niet in de xml te staan, maar alleen de version: < ?xml version="1.0"? >. MSXML print nooit de encoding.
    //      Kan dit problemen geven?
    var FCLTElement = xmlDoc.createElement("facilitor");

    var FCLTHeader = xmlDoc.createElement("header");

    var param;
    var info = { //ID: this.ID,
                 file: String(Request.ServerVariables("SCRIPT_NAME")).toLowerCase(),
                 dateTime: toDateTimeString(new Date(), true),
                 dateDay: (new Date).getDate(),
                 dateMonth: (new Date).getMonth()+1,
                 dateYear: (new Date).getFullYear(),
                 user: customerId,
                 rapportmode: getQParam("rapportmode", null), // internal abuse only
                 custId: customerId,
                 language: user.lang()
                 // ....
               }
    for (param in info)
    {
        if (info[param] != null)
        {
            var FCLTdata = xmlDoc.createElement(param);
            FCLTdata.appendChild(xmlDoc.createTextNode(info[param]));
            FCLTHeader.appendChild(FCLTdata);
        }
    }
    FCLTElement.appendChild(FCLTHeader);

    var rootElement = xmlDoc.createElement("resultset");
    rootElement.setAttribute("id", this.ID);

  with(this)
  {
    var oRs = Oracle.Execute(sql);
    var theSqlFlex = "";
    if (this.printFlex)
        theSqlFlex = getSqlFlex(flexModule.toUpperCase(), flexId, this.myRs(flexParams, oRs));
    for (var cnt = 0; !oRs.EOF; cnt++)
    {
        // Vooralsnog geen flex
        var elementRecord = xmlDoc.createElement("data_row");

        for (var i = 0; i < columns.length; i++)
        {
            var fieldname;
            if (columns[i].orgContent instanceof Function)
            {
                fieldname = columns[i].orgContent.toString().match(/function\s*(\w*)/)[1];
            }
            else
            {
                fieldname = columns[i].orgContent;
            }
            var elementField = xmlDoc.createElement(fieldname.toLowerCase() /*"field"*/);
            try
            {
              var val = (columns[i].content != null ? columns[i].content(oRs, outputmode) : "");
            }
            catch (e)
            {
              __Log("resultset_table_v2.inc: Probleem met het ophalen van columns[" + i + "]: " + columns[i].caption);
              throw(e);
            }
            var elementFieldText = xmlDoc.createTextNode(val);
            elementField.appendChild(elementFieldText);
            elementRecord.appendChild(elementField);
        }
        if (this.printFlex)
        {
            var flexKey = oRs(flexId).Value;
            completeSqlFlex = "SELECT * FROM (" + theSqlFlex + ")"
                            + " WHERE flexparentkey = " + flexKey // De flexKey invullen in de sqlFlex query
                            + " ORDER BY volgnummer, omschrijving";

            var oRsFlex = Oracle.Execute(completeSqlFlex);
            while (!oRsFlex.EOF)
            {
                if (oRsFlex("waarde").Value)
                {
                    var elementField = xmlDoc.createElement("kenmerk");
                    //completeSqlFlex bevat (helaas nog) geen "kenmerk_key"
                    var info = ["omschrijving", "kenmerktype", "nmin", "nmax", "volgnummer", "dimensie", "decimalen"];
                    for (var param in info)
                    {
                        if (oRsFlex(info[param]).Value)
                            elementField.setAttribute(info[param], oRsFlex(info[param]).Value);
                    }

                    var elementFieldText = xmlDoc.createTextNode(flexValue(oRsFlex));
                    elementField.appendChild(elementFieldText);
                    elementRecord.appendChild(elementField);
                }
                oRsFlex.MoveNext();
            }
            oRsFlex.Close();
        }

        rootElement.appendChild(elementRecord);
        oRs.MoveNext();
    }
    oRs.close();
    FCLTElement.appendChild(rootElement);
    xmlDoc.appendChild(FCLTElement);

    Response.ContentType = "text/xml";
    Response.Clear();

    if (Session("logging")&1)
    {
          var in_file = Server.MapPath("../../") + "/temp/";
          in_file = in_file + customerId + this.ID + ".xml";
          var fs  = Server.CreateObject("Scripting.FileSystemObject");
          var ts  = fs.CreateTextFile(in_file, true);
          ts.WriteLine (xmlDoc.xml);
          ts.Close();
    }

    Response.ContentType = "text/html";
    var xslfile = Server.MapPath(custpath+'\\xsl\\cust.xsl');

    var xslt = new ActiveXObject("Msxml2.XSLTemplate.6.0");
    var xsldoc = new ActiveXObject("Msxml2.FreeThreadedDOMDocument.6.0");
    var xslproc;
    xsldoc.async = false;
    xsldoc.setProperty("AllowXsltScript", true);
    xsldoc.load(xslfile);
    xsldoc.resolveExternals = true;
    xslt.stylesheet = xsldoc;
    xslproc = xslt.createProcessor();
    xslproc.input = xmlDoc;
    //xslproc.addParameter("mode", mode);
    //xslproc.addParameter("srtnotificatiecode", srtnotificatie);
    xslproc.output = Response;
    xslproc.transform();
    Response.End;
  }
}

ResultsetTable.prototype.ProcessAsCSV = function _ProcessAsCSV()
{
    var oRs;
    if (this.pRs)
        oRs = this.pRs;
    else if (this.dataset)
        oRs = new data_recordset( this.dataset );
    else
        oRs = Oracle.Execute(this.sql);
    // Header row
    var safeheader = [];
    for (var i = 0; i < this.columns.length; i++)
        safeheader.push(this.columns[i].caption);
    Response.Clear();
    Response.Write(safeheader.join(";") + "\r\n");

    while (!oRs.EOF)
    {
        var saferow = [];
        for (var i = 0; i < this.columns.length; i++)
        {
            try
            {
              var val = this.columns[i].content(oRs, this.outputmode);
            }
            catch (e)
            {
              __Log("resultset_table_v2.inc: Probleem met het ophalen van columns[" + i + "]: " + this.columns[i].caption);
              throw(e);
            }
            saferow.push(safe.csv(val));
        }

        Response.Write(saferow.join(";") + "\r\n");

        oRs.MoveNext();
    }
    oRs.close();

    Response.ContentType = "text/csv";
    Response.End;
}

ResultsetTable.prototype.countHistogram = function(oRs)
{
    var i, kolomnaam, kolomval, cnt;
    for (i= 0; i < oRs.Fields.Count; i++)
    {
        kolomnaam = oRs.Fields(i).Name;
        if (kolomnaam.toUpperCase() == this.keyColumn.toUpperCase())
            continue;
        kolomval = oRs.Fields(i).Value;
        if (!this.Counter[kolomnaam])
            this.Counter[kolomnaam] = {};
        if (!this.Counter[kolomnaam][kolomval])
            this.Counter[kolomnaam][kolomval] = 1;
        else
            this.Counter[kolomnaam][kolomval] += 1
    }
}

function __rsProcessResultset(processParams) // processParams wordt blind aan alle callbackfuncties gegevens als tweede parameter
{
  if (this.sql == null && !this.dataset)
    return;

  this.processParams = processParams;

  this.noPrint |= !device.test(device.canPrint);
  this.noExcel |= !device.test(device.canExcel);

  if (isXMLXSL)
  {
    this.ProcessAsXMLXSL();
    return; // die is zo compleet anders....
  }

  if (isCSV)
  {
    this.ProcessAsCSV();
    return; // die is zo compleet anders....
  }

  if (this.canBookmark)
  {
      if (Request.ServerVariables("REQUEST_METHOD") == "POST") // POST is nog te lastig
          INTERNAL_ERROR_CANNOT_BOOKMARK_POST;

      if (book_id || getQParam("bybookmark",0)==1)
          this.canBookmark = false;
  }

  with(this)
  {
    if (isPrinting)
      startPrint();

    // Print filter tabel met filterwaarden van het zoekscherm
    var html_filter = "";
    if (isPrinting == true)
    {
      if (filterParams)
        html_filter = getRSFilterTable(filterParams);
    }
    Response.Write(html_filter);

    // class moet hetzelfde zijn zodat summary text overal (incl inline overzichten) hetzelfde uitelijk krijgen
    // id moet verschillend zijn omdat je meerdere rsSummary divs onafhankelijk van elkaar moet benaderen (vullen)

    if (isPrinting)
    {
%>
      <div id="rsSummary<%=summaryId%>" class="rsSummary" style="display:none">
        <span id="rsSummaryInfo<%=summaryId%>" class="rsSummaryInfo"></span>
      </div>
      <script>
        function setSummary(html)
        {
           if (html != "")
           {
             var elm = document.getElementById("rsSummaryInfo<%=summaryId%>");
             elm.innerHTML = html;
             var elm = document.getElementById("rsSummary<%=summaryId%>");
             elm.style.display='block';
           }
        }
      </script>
<%
    }
    else if (isPlain)
    {
      if (this.rstableNr == 1)
      {
%>
      <script>
        var postformHTML = '<%=safe.jsstring(postformHTML)%>'; <% /* postformHTML naar javascript voor gebruik in resultset_table_v2.js */ %>
        var /*function*/ setSummary = FcltMgr.setHeaderExtraTitle;
      </script>
<%
      }
      if (this.canBookmark)
      {
%>
        <script>
          function saveBookmark()
          {
            var bmtitel = $("#bmtitel").val();
            // var bmadvanced = $("#bmadvanced")[0].checked;
            $("#showbookmark").hide();
            var data = { path: "<%=safe.jsstring(String(Request.ServerVariables("SCRIPT_NAME")))%>",
                         query: "outputmode=" + (<%=this.advancedprint?3:outputmode%>) + "<%=safe.jsstring(transitQS())%>",
                         naam: bmtitel
                       };
            <% protectRequest.dataToken("data"); %>
            $.post("../shared/create_bookmark.asp",
                   data,
                   FcltCallback, "json");
          }
          function createBookmark(evt, deze)
          {
            $("#showbookmark").show();
            $("#showbookmark").css("left", evt.clientX - $("#showbookmark").width() - 50);
          }
        </script>
<%
      }
      if (!noPrint)
      {
          if (this.rstableNr == 1)
          {
%>
        <script>
          function doPrint(poutputmode)
          {
              var autosortby = "";
              var xx = $("th.sorttable_sorted,th.sorttable_sorted_reverse");
              if (xx.length)
              {
                  if (xx.html().split("<br>").length > 1)
                  {
                      autosortby = xx.html().split("<br>")[0];// Bij combined alleen de eerste
                  }
                  else // moeten we complexer strippen
                  {
                      autosortby = xx.text();
                      autosortby = autosortby.substring(0, autosortby.length - 2); // indicator er af
                  }
              }
              if (xx.hasClass("sorttable_sorted_reverse"))
              {
                  autosortby = "-" + autosortby;
              }

<%            if (Request.ServerVariables("REQUEST_METHOD") == "POST")
              { %>
                document.printform.autosortby.value = autosortby;
                document.printform.outputmode.value = poutputmode;
                document.printform.submit();
<%            }
              else
              { %>
                window.open("<%=Request.ServerVariables("SCRIPT_NAME")%>?outputmode=" + poutputmode + "&autosortby=" + escape(autosortby) + "<%=safe.jsstring(transitQS())%>");
<%            } %>
          }

          function doOutput(poutputmode)
          {

<%          if (Request.ServerVariables("REQUEST_METHOD") == "POST")
            { %>
              // Alle parameters zijn in een formulier gestopt als request methode POST is.
              if (poutputmode == 0)
                document.printform.target = "_self";
<%          }
            var hasFlex = this.flexModule && this.flexId;
%>
            if (!<%=hasFlex?"true":"false"%> || !(poutputmode == 1 || (poutputmode == 2 && "<%=S("excel_mode")%>" == "0")))
            {
                doPrint(poutputmode);
                return;
            }
            params = { oktekst: L("lcl_Yes"), title: L(poutputmode==1?"lcl_print":"lcl_export_to_excel") };
            params.fncallback = function ()
                                {
                                    doPrint(poutputmode); // poutputmode wordt 5 (printer zonder flex) of 6 (excel zonder flex);
                                    // close gaat vanzelf;
                                }

            params.buttons = [
                {
                    text: L("lcl_No"),
                    click: function()
                    {
                        doPrint(poutputmode + 4);
                    }
                },
                {
                    text: L("lcl_cancel")
                }];
            FcltMgr.mydialog(L("lcl_print_properties"), params);
          }
        </script>
<%
          }
      }

      if (book_id)
      {
        this.actions = [];
        this.buttons = [];
      }

      buttons = this.buttons || [];
      if (!noPrint && Request.ServerVariables("REQUEST_METHOD") == "GET") // Alleen dan auto-refreshknop
      {
        var found = false;
        for (var i=0; i < buttons.length; i++)
        {
            if (buttons[i].title == L("lcl_refresh") || buttons[i].action == "FcltMgr.reload()")
                found = true;
        }
        if (!found)
            buttons.push({ icon: "fa-refresh", title: L("lcl_refresh"), action: "FcltMgr.reload()", id: "btn_scf_refresh" });
      }
      if (!noPrint)
      {
        buttons = buttons.concat([ { icon: "fa-print", title: L("lcl_print_table"),     action: "doOutput(1)" } ]);
        if (!noExcel)
        {
            buttons = buttons.concat([ { icon: "fa-table", title: L("lcl_export_to_excel"), action: "doOutput(2)" } ]);
        }
      }
      if (this.advancedprint)
      {
          buttons.push({ icon: "vcard.png",  title: advancedprint_title, action: "doOutput(3)" }); // advanced
      }

      if (this.canCSV && !noExcel)
      {
          buttons.push({ icon: "csv.png", title: L("lcl_export_to_csv"), action: "doOutput(4)" }); // CSV
      }

      if (this.canBookmark)
        buttons.push({ icon: "bookmark.png", title: L("lcl_fac_bookmark"), action: "createBookmark(window.event, this)" });

      if (this.title || buttons.length > 0)
         IFRAMER_HEADER(this.title || "", buttons);

      if (this.rstableNr == 1)
      { // collapseblock sluiten we nooit omdat we niet weten of er nog een volgende tabel komen.
        // Een browser sluit dan vanzelf aan het einde en dan klopt het weer. Niet perfect maar ach..
%>
        <div id="collapseblock">
<%
      }

      if (this.canBookmark)
      {  // JGL: Geen jQuery dialoog omdat dan resultset_table_v2 altijd jQuery-UI zou vereisen
%>
    <div id="showbookmark" style='padding:10px;border:2px solid black;position:absolute;top:5px;left:500px;background-color:#f0f0f0;display:none'>
        <input id='bmtitel' style='width:300px' type='text' value='<%=safe.htmlattr(this.title)%>'><br>
        <% if (false && this.advancedprint) { %>
            <label>
                <input type='checkbox' id='bmadvanced' value='1'>
                <%="lcl Speciaal linken naar {0} (anders gewoon)".format(safe.html(advancedprint_title))%>
            </label><br>
        <% } %>
       <input type='button' class='button' value='<%=L("lcl_fac_bookmark")%>' onclick='saveBookmark()'>
       <input type='button' class='button' value='<%=L("lcl_cancel")%>' onclick='$("#showbookmark").hide()'>
    </div>
<%    } // canBookmark
    } // isPlain

    var html = "";

    if (isPlain)
    {
        // ========= ACTIONS =========
        // == zijn er multi ACTIONS?
        var anyMultiActions = false;
        for (var i=0; i<this.actions.length; i++)
        {
              anyMultiActions |= this.actions[i].multi;
        }


        // Eerst: maak één div met alle acties.
        var hasActionsCol = 0;
        var skip = 0;
        for (var i=0; i < columns.length; i++)
        {
            if (columns[i].combine)
                skip ++;
            if (columns[i].hasActions)
            {
                hasActionsCol = i - skip;
                break;
            }
        }
        if (anyMultiActions)
            hasActionsCol++;
        if (this.inlineDetails)
            hasActionsCol++;

        // Nu de inline buttons renderen. Ze zijn er één keer en worden voor alle regels hergebruikt
        var actionsDiv = "";
        var aantalInline = 0;
        for (var i = 0; i < this.actions.length; i++)
        {
            if (this.actions[i].single === false) // triple '=' omdat undefined niet false mag zijn
                continue; // niet ook nog inline
            if (!this.actions[i].isDefault && !this.actions[i].onlyMulti)
                aantalInline++;
            var def = (this.actions[i].isDefault? " isdefault='1'" : "")
                    + (this.actions[i].onlyMulti? " onlymulti='1'" : "")
                    + (this.actions[i].isDefault || this.actions[i].onlyMulti? " style='display:none'" : "");
            var disp = useHamburger != 0 ? " style='display: block;'" : "";
            if (this.actions[i].multiOnce)
                val = "\n<span class='ia2' " + def + " onclick='doAm(event, this, \"" + this.actions[i].action + "\")'" + disp + ">";
            else
                val = "\n<span class='ia2' " + def + " onclick='doA(event, this, \"" + this.actions[i].action + "\")'" + disp + ">";
            actionsDiv += val + this.actions[i].caption + "</span>";
        }
        hasInlineActions = aantalInline > 0;
        html += "\n<div id='allactions_" + ID + "' class='allactions' style='display:none;z-index:13' hasActionsCol='"+hasActionsCol+"' >" + actionsDiv + "</div>";
        html += "\n<script type='text/javascript'>"
        html += "\n    initActions('" + this.ID + "', "+useHamburger+")"
        html += "</script>"
    }
    html += startTable();

    if (!Response.IsClientConnected)
    {
        __Log("Response.IsClientConnected false, Skipping sql query.", "#00FF00");
        Response.End;
    }

    // vanuit fac_usrrap_list krijgen we een open recordsset pRs door
    // hij moest hem zelf al openen om de kolommen te bepalen
    var oRs;
    if (this.pRs)
        oRs = this.pRs;
    else if (this.dataset)
        oRs = new data_recordset( this.dataset );
    else
        oRs = Oracle.Execute(sql);

    if (!Response.IsClientConnected)
    {   // Gebruiker was te ongeduldig. Hoeven we de rest ook niet meer te doen
        __Log("Response.IsClientConnected false, discarding result.", "#FFFF00");
        Response.End;
    }

    if (oRs == null || oRs.EOF)
    {
        Response.Write("<div id='rstable'><table><tr><td>" + emptySetString + "</td></tr>");
        endTable();
        if (isPrinting)
          endPrint();

        return -1;
    }

    var printedFirstHeader = false; // Is de eerste header afgedrukt?. Bij isExcel en isPrinting worden namelijk meerdere headers afgedrukt
    var printMoreHeaders = false; // Wordt gebruikt bij isExcel en isPrinting om meerdere headers af te drukken, maar dan moet de flexModule en flexId wel gedefineerd zijn
    // Alleen extra headers weergeven bij printen naar printer of excel
    // en indien de flexkenmerkquery, flexid en flexgroupid is meegegeven
    if ((isPrinting || isExcel || showProperties) && this.printFlex && flexGroupId)
      printMoreHeaders = true;
    var lastFlexGroupKey = -1; // Wordt gebruikt bij isExcel. Start without a previous flex group, the first one is new.
    var lastFlexParentGroupKey = -1 // Wordt gebruikt bij isExcel. Start without a previous flex group, the first one is new.
    var flexGroupChanged = false;

    var theSqlFlex = "";
    if (this.printFlex)
        theSqlFlex = getSqlFlex(flexModule.toUpperCase(), flexId, (flexParams instanceof Function ? flexParams(oRs) : flexParams));

    var lastHadFlex = false;
    for (var cnt = 0; (cnt < S("qp_maxrows") || isPrinting || isExcel || showAll || showProperties) &&
                      (cnt < S("qp_maxrows2") || isExcel) && // Naar excel altijd alle regels (meer dan S("qp_maxrows2")) weergeven.
                      !oRs.EOF; cnt++)
    {
      var oRsFlex = null;
      if ((isPrinting || isExcel || showProperties) && this.printFlex)
      {
        var flexKey = oRs(flexId).Value;
        var oRsFlexData = getFlexData(theSqlFlex, flexKey, showProperties);  // is een JSON

        if (!oRsFlexData.length)
          var noFlexResult = true; // Er zijn geen flexkenmerken
        else
          var noFlexResult = false; // Er zijn flexkenmerken

        if (flexGroupId) // Is er een flexGroupId meegegeven
        {
          var flexGroupKey = myRs(flexGroupId, oRs);
          flexGroupChanged = isExcel && (lastFlexGroupKey != flexGroupKey); // Alleen bij printen naar excel moet er wellicht een nieuwe header boven i.v.m. andere flexkenmerken
          var flexChanged = flexGroupChanged;
          lastFlexGroupKey = flexGroupKey;

          if (flexParentGroupId) // Is er een flexParentGroupId meegegeven
          {
            var flexParentGroupKey = myRs(flexParentGroupId, oRs);
            flexParentGroupChanged = isExcel && (lastFlexParentGroupKey != flexParentGroupKey); // Alleen bij printen naar excel moet er wellicht een nieuwe header boven i.v.m. andere flexkenmerken
            if (!flexParentGroupChanged && flexGroupChanged && flexChangeNiveau && !noFlexResult)
            { // Flexgroep is veranderd maar parentflexgroep niet. Alleen als er op flexgroep niveau flexkenmerken zijn gedefinieerd is er een nieuwe header nodig.
              flexChanged = false;
              for (var datai=0; i < oRsFlexData.length; i++)
              {
                var rec = oRsFlexData[datai];
                var kenmerk_niveau = rec.niveau;
                if (kenmerk_niveau == flexChangeNiveau)
                {
                  flexChanged = true;
                  break;
                }
              }
            }
            flexChanged = flexParentGroupChanged || flexChanged; // Als parent group veranderd is zijn er waarchijnlijk ook andere kenmerken. Kan voor meldingen nog scherper i.v.m. nog een niveau (parent) hoger. Maar is in praktijk nog niet echt nodig.
            lastFlexParentGroupKey = flexParentGroupKey;
          }
        }
      }

      if (!printedFirstHeader || (printMoreHeaders && flexChanged && (!noFlexResult || (lastHadFlex && noFlexResult))))
      {
        // Header van ResultsetTable
        html += makeTableHeader(oRs, oRsFlexData, anyMultiActions);
        html += "\n<tbody>";
        printedFirstHeader = true;
      }
      lastHadFlex = !noFlexResult;

      // Een row van ResultsetTable
      html += makeTableRow(oRs, oRsFlexData, cnt, anyMultiActions, noFlexResult);
      if (this.buildHistogram)
        this.countHistogram(oRs);

      // Voer berekeningen uit i.v.m. extra info die moet komen in de regel waar ook de print en excel iconen staan
      if (summaryCalc)
        summaryCalc(oRs, this.processParams);

      if (totalCalc)
        totalCalc(oRs, this.processParams);

      if (isExcel) // Excel heeft geen S("qp_maxrows2") bescherming. Extreem grote rapportages leiden
      {            // tot een out-of-memory. Omdat bij Excel verderop toch niets wordt gedaan met html
                   // gooien we die hier alvast naar buiten zodat Response Buffer Exceeded wel ingrijpt.
        Response.Write(html);
        html = "";
      }
      oRs.MoveNext();
    }

    html += "</tbody>";

    if ((!oRs.EOF || this.hasMore) && !isPrinting && !isExcel)
    {
      var a_element = "";
      if (Request.ServerVariables("REQUEST_METHOD") == "POST")
      {
        // Inline details met POST methode en meer dan max regels tonen werkt niet. Komt ook niet voor.
        a_element = "<a href='javascript:doOutput(0)'>";
      }
      else
      {

        a_element = "<a href='"
                      + (inline // Voor inline details moet niet gehele window vervangen worden omdat je dan ook de hoofd regels kwijt bent (dan alleen de <td> cell vervangen)
                        ? "javascript:loadInlineDetails(\""
                          + safe.jsstring(Request.ServerVariables("SCRIPT_NAME") + "?showall=1" + transitQS())
                          + "\");"
                        : safe.jsstring(Request.ServerVariables("SCRIPT_NAME") + "?showall=1" + transitQS()) + "' target='_self")
                      + "'>";
      }
      a_element += L("lcl_qp_maxrows1") + S("qp_maxrows") + L("lcl_qp_maxrows2")+"</a>";

      // Op regel klikken om de overige regels op te halen, boven de tabel
      html = "<div id='rsMaxHistoryB'><span id='rsMaxHistoryInfoB'>"
           + (cnt <= S("qp_maxrows")
             ? a_element.replace(/&collapsed=1/gi, "") // Als er op deze regel wordt geklikt moet IFRAMER niet meer dichtgeklapt worden, dus &collapsed=1 verwijderen uit de link.
             : L("lcl_qp_maxrows1") + S("qp_maxrows2") + L("lcl_qp_maxrows3")
             )
           + "</span></div>" + html;
    }

    // Footnote van ResultsetTable
    // Footnote hoort eigenlijk voor de tbody (HTML4.0) maar dan weten we het totaal nog niet.
    // MGE: TODO: Footnote kan hier nog voor de body gezet worden want Response.Write(html) heeft nog niet plaatsgevonden!
    //      Echter bij (printMoreHeaders && flexGroupChanged) worden er meerdere headers geprint op verschillende plekken.
    //      Dit hoort eigenlijk niet zo.
    var htmlFoot = "";
    if (oRs.EOF)
      htmlFoot += makeTableFootnote(anyMultiActions, oRsFlexData);

    if (anyMultiActions && !inline)
    {
        appender = "\n<div id='multiactiondiv' style='float:left; visibility:hidden;'><span id='multilabel'>" + L("lcl_with_selected").format(0) + "</span>";
        var multiActions = 0;
        for (var i=0; i < actions.length; i++)
        {
            if (actions[i].multi && (actions[i].okformulti || actions[i].onlyMulti || actions[i].single === false))
            {
                multiActions++;
                appender += "<span onClick='javascript:doMulti(\"" + this.ID + "\", \"" +
                                                                    actions[i].action + "\", \"" +
                                                                    actions[i].caption + "\", " +
                                                                    actions[i].multiOnce + ");" +
                                                                   "FcltMgr.stopPropagation(event);' class='button footerbutton'>"+actions[i].caption+"</span>";
             }
        }
        if (multiActions == 0)
            appender += "<i style=\"padding-left: 9px\">" + L("lcl_no_multi_actions") + "</i>";
        appender += "</div>";
        htmlFoot += "<tr><td class='rsfooter' colspan=" + (columns.length + ((useHamburger && hasInlineActions) ? 1 : 0) + (!isPrinting && !isExcel && anyMultiActions && !inline? 1 : 0)) + ">" + appender + "</td></tr>";
    }

    if (htmlFoot != "")
      htmlFoot = "<tfoot>" + htmlFoot + "</tfoot>";

    html += htmlFoot;

    Response.Write(html);

    endTable();

    // 'Klik voor meer' onderaan de tabel
    if (!oRs.EOF || this.hasMore)
    {
      var a_element = "";
      if (Request.ServerVariables("REQUEST_METHOD") == "POST")
      {
        // Inline details met POST methode en meer dan max regels tonen werkt niet. Komt ook niet voor.
        a_element = "<a href='javascript:doOutput(0)'>";
      }
      else
      {
        a_element = "<a href='"
                      + (inline // Voor inline details moet niet gehele window vervangen worden omdat je dan ook de hoofd regels kwijt bent (dan alleen de <td> cell vervangen)
                        ? "javascript:loadInlineDetails(\""
                          + safe.jsstring(Request.ServerVariables("SCRIPT_NAME") + "?showall=1" + transitQS())
                          + "\");"
                        : safe.jsstring(Request.ServerVariables("SCRIPT_NAME") + "?showall=1" + transitQS()) + "' target='_self")
                      + "'>";
      }
      a_element += L("lcl_qp_maxrows1") + S("qp_maxrows") + L("lcl_qp_maxrows2")+"</a>";

      // Op regel klikken om de overige regels op te halen, onder de tabel
      html = "<div id='rsMaxHistoryE'><span id='rsMaxHistoryInfoE'>"
           + (cnt <= S("qp_maxrows")
             ? a_element
             : L("lcl_qp_maxrows1") + S("qp_maxrows2") + L("lcl_qp_maxrows3")
             )
           + "</span></div>";

      Response.Write(html);
    }

    if (!isExcel && oRs.EOF && !this.hasMore)
    {
      // MGE: Alleen sortable als alle regels getoond zijn en niet bij excel
%>
      <script>
        if (document.getElementById("<%=ID%>")) document.getElementById("<%=ID%>").className = "<%=tableClassName + " sortable"%>";
      </script>
<%
    }

    // default summary text
    if (rowNum == 1)
        summaryInfo = L("lcl_nr_lines_result1one") + rowNum + L("lcl_nr_lines_result2one");
    else
        summaryInfo = L("lcl_nr_lines_result1") + rowNum + L("lcl_nr_lines_result2");

    if (summaryShow)
    {
      var summaryInfo = ((!oRs.EOF || this.hasMore)? "<span class='summaryincomplete'>" + L("lcl_suminfo_incomplete") + "</span>&nbsp;" : "") + (summaryShow(rowNum) || "");
    }
    if (!this.noSetSummary)
    {
%>    <script>
        setSummary("<%=safe.jsstring(summaryInfo)%>");
      </script>
<%  }

    // Eventueel doortellen tot het einde. Dat doen we maximaal 1 seconde! (zo'n 3200 records)
    var tmStart = (new Date).getTime();
    var teller = 0;
    while (!oRs.EOF && this.buildHistogram && ((new Date).getTime() - tmStart) < 1000)
    {
      teller ++;
      this.countHistogram(oRs);
      oRs.MoveNext();
    }
    if (!oRs.EOF && this.buildHistogram)
    { // Niet gered binnen een seconde
      __Log("Histogram niet binnen 1000ms klaar. Reset histogram");
      this.Counter = {};
    }
    else if (teller > 0)
    {
      __Log("Histogram afgerond ("+teller+" extra items) in " + ((new Date).getTime() - tmStart) + "ms");
    }
    oRs.close();

    if (endText)
    {
      var endTextInfo = (endText instanceof Function ? endText(oRs, this.processParams) : endText);
      if (endTextInfo != "")
      {
%>
        <div id="rsEndText">
          <span id="rsEndTextInfo"><%=endTextInfo%></span>
        </div>
  </div>
<%
      }
    }

    if (isPrinting)
      endPrint();

    if (this.buildHistogram)
        return this.Counter;
    else
        return rowNum; // simpel return aantal rows
  } // with(this)
}

function __rsStartTable()
{
  var html = "";
  with (this)
  {
    html = "<div id='rstable'>";
    html += "<table cellspacing='0' cellpadding='0' ";

    if (isPrinting == true) {
      html += " isPrinting='TRUE' ";
    }

    if (ID != null)
      html += " ID='" + ID + "' ";
    if (tableClassName != null)
      // PF vervangen: html += " CLASS='" + tableClassName + (this.actions.length? "" : " sortable") + "' ";
      // MGE sortable wordt later gezet als ook bekend is of alle regels zijn getoond.
      html += " CLASS='" + tableClassName + "' ";
    html += ">";

    rowNum = 0;
  }
  return html;
}

function __rsMakeTableHeader(oRs, oRsFlexData, anyMultiActions)
{
  var html = "";
  with(this)
  {
    html += "\n<thead><tr>";
    if (!isPrinting && !isExcel)
    {
        // ========= ACTIONS =========
        // == zijn er multi ACTIONS?
        if (anyMultiActions && !inline)
        {
            html += "<th class='multiselect sorttable_nosort'><input type=checkbox class='master' onclick='checkAll(\"" + ID + "\", this)'></th>";
        }
        if (useHamburger && hasInlineActions)
        {
            html += "<th class='sorttable_nosort'></th>";
        }
        if (inlineDetails)
        {
            html += "<th>"+L("lcl_inlinecolumnheader")+"</th>";
        }
    }
    // ========= COLUMNS/THEADER =========
    for (var i=0; i < columns.length; i++)
    {
        if (propertiesColumn == i)
          // Nog kolommen toevoegen voor de flexkenmerken
          html += addFlexKenmerkHeader(oRsFlexData, this);

        if ((!isPrinting && (columns[i].purpose & VIEW_ONLY)) ||
             ((isPrinting || showProperties || isExcel) && (columns[i].purpose & PRINTING_ONLY)))
        {
          if ((isPrinting || showProperties || isExcel || !columns[i].combine) && !(isExcel && columns[i].hidden))
          {
              html += "<th";
              if (columns[i].caption == p_autosortby)
              {
                html += " id='autosortbyme'";
              }
              var sort_class = "";
              if (columns[i].datatype == "currency" ||
                  columns[i].datatype == "float" ||
                  columns[i].datatype == "number")
              {
                  html += " style='text-align:right;'";
                  if (safe.curr(1.5).indexOf(",") == -1)
                    sort_class += " sortable_numeric";
                  else
                    sort_class += " sorttable_numericcomma";
              }
              if (columns[i].align)
                html += " style='text-align:" + columns[i].align + "'";

              sort_class += columns[i].thClass? " " + columns[i].thClass : "";
              if (sort_class != null)
                html += " CLASS='" + sort_class + "' ";
              if (inlineDetails)
                html += " preSorttable='hideAllInlineDetails(\""+ID+"\")'";
              var hidden = columns[i].hidden ? (" style='display:none'") : "";
              var title = columns[i].title == null ? "" : (" title='" + safe.htmlattr(columns[i].title) + "'");
              html += hidden + title + ">"
          }
          if (!(isExcel && columns[i].hidden))
          {
            if (columns[i].combine && columns[i].caption && !isPrinting)
              html += "<br>";
            html += columns[i].caption;
          }

          // Is de volgende kolom een combine. LET OP!!!: PRINTING_ONLY kolommen niet meerekenen (overslaan) in niet print uitvoer.
          var combine = false;
          for (var c = i + 1; c < columns.length; c++)
          {
            if (columns[c].purpose != PRINTING_ONLY)
            {
              combine = columns[c].combine;
              break;
            }
          }

          if ((!isPrinting && !isExcel && (i < columns.length - 1) && combine) || (isExcel && columns[i].hidden))
          {
              // html += "<br>";
            }
          else
              html += "</th>";
        }
    }

    if (propertiesColumn == null)
      // Nog header toevoegen voor de flexkenmerken.
      html += addFlexKenmerkHeader(oRsFlexData, this);

    html += "</tr></thead>";
  }
  return html;
}

var lastRowKey = -1; // voor suppress
function __rsMakeTableRow(oRs, oRsFlexData, cnt, anyMultiActions, noFlexResult)
{
  var html = "";
  // Pas op: dit komt voor elke regel terug, dus hou het kort!
  with(this)
  {
        var thisKey = -1;
        if (suppressKeyRepeat)
            thisKey = oRs(suppressKeyRepeat).Value;

        // HIER START EEN NIEUWE REGEL
        var appender = ("\n<tr");

        var OddRowClass = ""; // was:(cnt & 1)? "O " : "E "; maar dat kan inmiddels met css clientside beter

        if (suppressKeyRepeat)
          if (lastRowKey!=thisKey)
              OddRowClass += "firstRow ";
          else
              OddRowClass += "suppressRow ";

        if (!isExcel)
        {
            if (rowClass)
              appender += " class='" + OddRowClass + rowClass(oRs, this.processParams) + "' ";
            else
              appender += " class='" + OddRowClass + "' ";
        }

        if (keyColumn != null && !isPrinting && !isExcel)
        {
          appender += " ROWKEY='" + safe.htmlattr(this.myRs(keyColumn, oRs)) + "'";
        }
        if (!isPrinting && !isExcel && rowData != null)
          appender += " ROWDATA='" + safe.htmlattr(rowData(oRs, this.processParams)) + "'";

        if (!isPrinting && dblClick != null)
          appender += " onDblClick='" + safe.htmlattr(dblClick) + "'";

        if (!isPrinting && !isExcel && this.actions && this.actions.length)
        {
          if (!keyColumn)
            INTERNAL_ERROR_ACTION_ZONDER_KEYCOLUMN; // dat kan niet echt, we hebben een ROWKEY nodig

          if (rowActionEnabler)
            var validator = rowActionEnabler(oRs, this.processParams);

          var actionbits = "";
          var noActionBit = "";

          for (var i = 0; i < actions.length; i++)
          {
              if (actions[i].single === false) // triple '=' omdat undefined niet false mag zijn
              {
                  // die negeren we altijd actionbits += "0";
                  continue; // niet ook nog inline
              }
              if (actions[i].enabler && validator && !validator[actions[i].enabler])
              {
                  actionbits += "0";
                  noActionBit += "0";
                  continue; // niet enabled
              }
              actionbits += "1"; // Enabled
              actions[i].okformulti = true;
              noActionBit += "0";
          }
          appender += " ACTIONBITS='" + safe.htmlattr(actionbits) + "'";
      }

      appender += (">");

        // Het <TR> start element is nu klaar

        // ========= COLUMNS =========
        if (!isPrinting && !isExcel && anyMultiActions && !inline) // Checkboxje erbij voor 'select all'
        {
            var isChecked = this.rowChecked && this.rowChecked(oRs, this.processParams);
            appender += "<td class='multiselect'><input type=checkbox class='multiselect'" + (isChecked?' checked=1':'') + "></td>";
        }

        if (useHamburger && hasInlineActions)
        {
            // TODO: If clause nog verbeteren
            if (actionbits.substr(1) == noActionBit.substr(1))
            {
                appender += "<td></td>";
            }
            else
            {
                appender += "<td class=\"hambCont\"><div class=\"hamburger\"><i class=\"fa fa-fw fa-lg fa-bars\"></i></div></td>";
            }
        }

        if (!isPrinting && !isExcel && inlineDetails)
        {
            var ttl = null;
            if (hasInlineDetails) ttl = hasInlineDetails(oRs, this.processParams);

            if (!hasInlineDetails || ttl)
            {
                if (typeof ttl == 'string')
                    ttl = " title ='" + safe.htmlattr(ttl) + "'"
                else
                    ttl = "";
                appender += "<td class='inlinedetails closed' onclick='return showInlineDetails(this, \"" + inlineDetails+"\")'" + ttl + "><i class='fa fa-fw fa-plus-square-o'></td>";
            }
            else
                appender += "<td>"+L("lcl_inlinecolumnnone")+"</td>";
        }

        // ========= De echte waarde =========
        var colHasVal;
        for (var i = 0; i < columns.length; i++)
        {
            if (propertiesColumn == i)
              // Nog kolommen toevoegen voor de flexkenmerken
              appender += addFlexKenmerkKolommen(oRsFlexData, this,
                                                 { lastRowKey: lastRowKey,
                                                   thisKey: thisKey
                                                 });

            if ((!isPrinting && (columns[i].purpose & VIEW_ONLY)) ||
                 ((isPrinting || showProperties || isExcel) && (columns[i].purpose & PRINTING_ONLY)))
            {
              if (!columns[i].combine)
                colHasVal = false;
              try
              {
                var val = (columns[i].content != null ? columns[i].content(oRs, this.processParams) : "");
              }
              catch (e)
              {
                __Log("resultset_table_v2.inc: Probleem met het ophalen van columns[" + i + "]: " + columns[i].caption);
                throw(e);
              }

              if (columns[i].image)
              {
                    var imgval = "<img src='" + columns[i].image + "'";
                    if (val) imgval += " alt='" + safe.htmlattr(val) + "'";
                    val = imgval + ">";
              }

              if (val == null) val = '&nbsp;';
              if (suppressKeyRepeat && lastRowKey==thisKey && !columns[i].keepKeyRepeat)
                val = '&nbsp;'
              // just in case?
              val = String(val);

              // Tooltip voor de kolom
              var tooltip = columns[i].tooltip == null ? null : this.myRs(columns[i].tooltip, oRs);
              if (!tooltip && columns[i].prettydate)
              {
                tooltip = toDateString(oRs(columns[i].orgContent).Value, isExcel, false);
              }
              tooltip = (tooltip == null? "" : " title='" + safe.htmlattr(tooltip) + "'");

              // Class voor de kolom
              var columnClass = columns[i].columnClass == null ? columns[i].tdClass : this.myRs(columns[i].columnClass, oRs);
              columnClass = (columnClass == null? "" : " class='" + columnClass + "'");

              // custom sort key voor de kolom
              var customSort = columns[i].customSort == null ? null : this.myRs(columns[i].customSort, oRs);
              if (!customSort && columns[i].prettydate)
              {
                customSort = toDateString(oRs(columns[i].orgContent).Value, isExcel, false);
              }
              customSort = (customSort == null? "" : " sorttable_customkey='" + customSort + "'"); // pikt sorttable.js vanzelf op

              // Uitlijning voor de kolom
              var align = "";
              if (columns[i].align)
                    align = " align='" + columns[i].align + "'";
              else
              {
                 if (columns[i].datatype == "currency" ||
                     columns[i].datatype == "float" ||
                     columns[i].datatype == "number")
                     align = " style='text-align:right;'";
              }

              // Niet zichtbare kolom
              var hidden = columns[i].hidden ? (" style='display:none'") : "";

              // Prevent wrapping if required
              var nowrap = columns[i].nowrap ? " class='nowrap'" : "";

              // Opbouw cell
              if ((isPrinting || showProperties || isExcel || !columns[i].combine) && !(isExcel && columns[i].hidden))
              {
                appender += "<td" + align + hidden + tooltip + columnClass + customSort + nowrap;
                if (isExcel && val != null && val.length > 0 && val.match(/[0-9]/)
                        && !inArray(columns[i].datatype, ["date", "currency", "datetime", "float", "number"]))
                {
                    appender += " style='mso-number-format:\"\@\"'";
                }
                appender += ">";
              }
              else
              {  // Combine maar geen <br> voor lege val
                 if (val != '&nbsp;' && val !== '' && colHasVal)
                 {
                  appender += "<br>"
                 }
                else
                    appender = appender.replace(/\&nbsp\;$/,"") // 'Vorige' &nbsp; er af strippen
              }

              if (!(isExcel && columns[i].hidden))
                appender += val;

              // Is de volgende kolom een combine. LET OP!!!: PRINTING_ONLY kolommen niet meerekenen (overslaan) in niet print uitvoer.
              var combine = false;
              for (var c = i + 1; c < columns.length; c++)
              {
                if (columns[c].purpose != PRINTING_ONLY)
                {
                  combine = columns[c].combine;
                  break;
                }
              }

              if ((!isPrinting  && !isExcel && (i < columns.length - 1) && combine) || (isExcel && columns[i].hidden))
                ;/* appender += "<br>"; */
              else
                appender += "</td>";

              // Hebben we al een echt waarde gehad?
              colHasVal = colHasVal || (val != '&nbsp;');

              // Als totaal van de kolom weergegeven moet worden dan waarden optellen.
              // amount is altijd een functie
              if (columns[i].total)
              {
                var amount = (columns[i].amount != null ? columns[i].amount(oRs, this.processParams) : null)
                if (amount != null && !isNaN(amount))
                    columns[i].totalsum += amount;
              }
            }
        }

        html += appender;

        if (propertiesColumn == null)
          // Nog kolommen toevoegen voor de flexkenmerken
          html += addFlexKenmerkKolommen(oRsFlexData, this,
                                         { lastRowKey: lastRowKey,
                                           thisKey: thisKey
                                         });

        html += "</tr>";
    rowNum = cnt + 1;
  }

  lastRowKey = thisKey;
  return html;
}

function __rsMakeTableFootnote(anyMultiActions, oRsFlexData)
{
  var html = "";
  with(this)
  {
    var total = false;
    // Kijk eerst of er een totaalregel toegevoegd moet worden
    for (var i=0; i < columns.length; i++)
    {
        if (columns[i].total) total = true;
    }
    if (total && !isExcel)
    {
      html += "<tr>";
      if (!isPrinting  && anyMultiActions && !inline) // Kolom i.v.m. checkboxje erbij voor 'select all'
        html += "<td></td>";
      if (hasInlineDetails && !isPrinting && !isExcel && inlineDetails)
        html += "<td></td>"; // Kolom i.v.m. inlinedetails
      if (useHamburger && hasInlineActions)
          html += "<td></td>"; // Ook nog kolom i.v.m. hamburger die er sowieso mogelijk alleen is als er acties zijn

      var html_f = "";
      for (var i = 0; i < columns.length; i++)
      {
          if ((!isPrinting && (columns[i].purpose & VIEW_ONLY)) ||
               ((isPrinting || showProperties || isExcel) && (columns[i].purpose & PRINTING_ONLY)))
          {
            if (!columns[i].combine || isPrinting || showProperties || isExcel) // Bij isPrinting, isExcel of showProperties niet combineren.
            {
              // Nog kolommen toevoegen voor flexkenmerken
              if (propertiesColumn == i)
                html_f += addFlexKenmerkFooter(oRsFlexData, this);

              html_f += "<td";
              var sort_class = columns[i].thClass? " " + columns[i].thClass : "";
              if (sort_class != null)
                html_f += " CLASS='" + sort_class + "' ";
              // Uitlijning voor de kolom
              var align = "";
              if (columns[i].align)
                    align = " align='" + columns[i].align + "'";
              else
              {
                 if (columns[i].datatype == "currency" ||
                     columns[i].datatype == "float" ||
                     columns[i].datatype == "number")
                     align = " style='text-align:right;'";
              }
              var hidden = columns[i].hidden ? (" style='display:none'") : "";
              html_f += align + hidden + ">";
            }
            // Weergeven als integer of als getal met twee decimalen.
            if (columns[i].total)
            {
              if (totalShow)
              {
                html_f += totalShow(columns[i], rowNum);
              }
              else
              {
                html_f += ResultsetTable.formatValue(columns[i].totalsum, columns[i]);
              }
            }
            else
              html_f += "&nbsp;";
            if ((i < columns.length - 1) && !columns[i + 1].combine)
              html_f += "</td>";
          }
      }

      // Nog extra kolom headers voor de flexkenmerken
      // Flexkenmerken tonen bij isPrinting, isExcel of showProperties
      if (this.printFlex && this.propertiesColumn == null)
      {
        html_f += "<td></td>";
      }

      html_f = balanceCloseTag("td", html_f);
      html += html_f + "</tr>";
    }
  }
  return html;
}

function balanceCloseTag(tagname, chtml)
{   // Als het aantal tag-open groter is dan het aantal tag-close dan een tag-close toevoegen.
    var tag_open  = chtml.toLowerCase().split("<"+tagname.toLowerCase());
    var tag_close = chtml.toLowerCase().split("/"+tagname.toLowerCase()+">");
    if (tag_open.length > tag_close.length)
    {
        chtml += "</"+tagname+">";
    }
    return chtml;
}

function __rsEndTable()
{
  Response.Write("</table>");
  Response.Write("</div> <!-- for DIV id='rstable' -->");
}

function __rsAddColumn(column)
{
    this.columns.push(column);
}

function __rsStartPrint()
{
  if (!isPrinting) return;

  var logo_fileName = custpath + "/tmpl_logo.gif";
  Response.Write('<div id="printheader">');
  Response.Write('<table id="head"><tr>');
  Response.Write('<td align="left"><img src="' + logo_fileName+ ' "></TD>');
  Response.Write('<td align="right">');
  Response.Write( '<table id="right">');
  Response.Write( '<tr><td colspan="2" id="titel">' + (this.title||"") + '</td></tr>');
  Response.Write( '<tr><td>' + L("lcl_name") + ' :</td><td>' + safe.html(user.naam()) + ' </td></tr>');
  Response.Write( '<tr><td>' + L("lcl_date") + ' :</td><td>' + toDateString(new Date()) + ' </td></tr>');
  Response.Write('</table>');
  Response.Write('</td>');
  Response.Write('</tr></table>');
  Response.Write('</div>');
}

function __rsEndPrint()
{
  if (isPrinting)
  {
    var prtDate = toDateTimeString(new Date());
    Response.Write('<div id="printfooter">');
    Response.Write('<table id="foot"><tr>');
    Response.Write( '<td>' + (this.title||"") + '</td>');  // deze this.title lijkt intussen verloren gegaan
    Response.Write( '<td align="right">' + toDateTimeString(new Date()) + '</td>');
    Response.Write( '</tr></table>');
    Response.Write('</div>');
    if (p_autosortby)
    {
        var sortfn = autosortbyrev?"sorttable.doSortRev":"sorttable.doSort";
        Response.Write('<script>$(function () { if ($("#autosortbyme").length) { '+sortfn+'.apply($("#autosortbyme")[0]) };window.print() });</script>');
    }
    else
        Response.Write('<script>window.print();</script>'); // Zonder jQuery
  }
}


///////////
// Column
///////////
var PRINTING_ONLY = 1;
var VIEW_ONLY = 2;
var PRINT_AND_VIEW = PRINTING_ONLY + VIEW_ONLY;

function Column(params)
{
  if (!params) params = {};
  this.caption = params.caption;
  this.title = params.title;
  this.filter = params.filter;
  this.content = params.content;
  this.image = params.image;
  this.action = params.action;
  this.tooltip = params.tooltip;
  this.thClass = params.thClass;
  this.columnClass = params.columnClass;
  this.customSort = params.customSort;
  this.align = params.align;
  this.purpose = params.purpose? params.purpose : PRINT_AND_VIEW;
  this.hidden = params.hidden;
  this.datatype = params.datatype;
  this.decimals = params.decimals;
  this.total = params.total;
  this.amount = null; // Voor INTERN gebruik: is de integerwaarde van content, indien de kolomwaarden opgeteld moeten worden.
  this.totalsum = 0;
  this.nowrap = params.nowrap;
  this.orgContent = params.content;
  this.nomidnight = params.nomidnight;

  // Neem alle meegegeven parameters mee.
  for (param in params)
  {
    if (param == "content" && params.content)
    {
       if (params.content instanceof Function)
       {
           this.content = params.content;
           this.amount  = params.fnAmount || params.content;
       }
       else
       {
           this.content = function (oRs) { return ResultsetTable.formatValue(oRs.Fields(params.content).Value, this)} ;
           this.amount = function (oRs) { return oRs.Fields(params.content).Value };
       }
    }
    else
      this[param] = params[param];
  }
  if (!isPlain && this.prettydate)
     this.prettydate = false;
}

// Levert een html-safe waarde op
ResultsetTable.formatValue = function (val, params) //datatype, decimals)
{
    if (val == null)
    {
        if (isXMLXSL || isCSV)
            return "";
        else
            return "&nbsp;";
    }
	// Vanuit API2 model
    if (typeof val == "object" && "name" in val)
    {
        val = val["name"];
        params.datatype = null; // default string
    }

    // Eerst de geforceerde datatypes
    switch (params.datatype)
    {
        case "date"    : return toDateString(val, isExcel||isCSV, params.prettydate); // isExcel-->noDay
        case "datetime": return toDateTimeString(val, false, isExcel||isCSV, params.prettydate);
        case "time"    : { val = toTimeString(val);
                           if (params.datatype == "time" && params.nomidnight && val == "00:00")
                             val = "";
                           return val;
                         }
        case "currency": return safe.curr(val);
        case "float"   : return safe.displayfloat(val, params.decimals);
        case "number"  : return val;
        case "html"    : return val;
    }

    // Autotypering "datum"
    if (typeof val == "date" || val instanceof Date)
        return toDateString(val, isExcel, params.prettydate);

    if (!val)
        return "";

    if (typeof val != "string")
    {   // Je raakt bijvoorbeeld de rechtse uitlijning van number-velden kwijt
        __Log("Ongespecificeerd datatype van veld '{0}'{1} is {2}, fallback naar string".format(params.caption, typeof params.orgContent=='string'?' (' + params.orgContent + ')':"", typeof val), "#FF0");
        val = String(val);
    }
    val = shared.stripbbcodes(val);

    if (isXMLXSL || isCSV)
        return val; // DOM lost encoding wel op of we doen safe.csv.

    // default datatype is "string"
    val = String(val).replace(/[\n\r]+/g,"\n"); // Compact, geen lege regels tonen

    if (isExcel)
    {   // Hier geen safe.html omdat we voor Excel de <br> naar \n vertaling niet kunnen hebben.
        // Met <br> krijg je een nieuwe rij in excel
        return Server.HTMLEncode(val);
    }

    if (isPrinting || S("rs_maxchar") < 0 || val.length < S("rs_maxchar") * 1.20) // 'Meer' moet minstens 20% extra tekst opleveren.
        return safe.html(val); // simpelweg hele tekst

    // Oke, op scherm afkappen na S("rs_maxchar") karakters.
    // Probeer op de eerste spatie maar die moet wel binnen 20% karakters komen
    var fullval = val;
    var spatie = String(val).substr(S("rs_maxchar")).indexOf(" ");
    if (spatie < 0 || spatie > S("rs_maxchar")/5)
        spatie = 0;
    val = fullval.substr(0, S("rs_maxchar")+spatie+1);
    // pas op: val is al 'html-safe' terwijl we dat voor afkappen niet kunnen hebben?
    // in ieder geval geen safe.htmlattr.
    var safeval = safe.html(val)
                + "<span class='rsTruncmore' " // Dubbel encoderen hieronder, anders html-injections was achter de Meer... mogelijk
                + " resttxt='" + safe.htmlattr(safe.html(fullval.substr(S("rs_maxchar")+spatie+1))) + "'"
                + " title='"+L("lcl_rs_truncmore")+"'"
                + " onclick='return expandTruncate(event, this)'"
                + ">"+L("lcl_rs_truncated")+"</span>";

    return safeval;
}

function ResultsetTable.prototype.myRs(fn, oRs)
{
    if (fn instanceof Function)
    {
        return fn(oRs, this.processParams);
    }
    else
    {
        return oRs.Fields(fn).Value;
    }

}
%>
