<% /*
     $Revision$
     $Id$


    File:               kenmerk_common.inc
    Description:
    Context:            Used by /XX/kenmerk.inc
    Note:               Writes flex-props html edit/view code to Response object
    Usage:              listKenmerk must be called once with an SQL-statement that
                        fetches all kenmerk-information
                        The structure of tha SQL should match the predefined structure (column names)

    TODO: HTML5-achtige zaken toevoegen
        - required is ook een attribute met functionaliteit. Tot dusverre zetten we alleen reuired_class
          moeten we behalve adt attr toevoegen ook iets met toggle en zo?

    FlexFiles.inc functie bepaalNiveau is altijd nodig dus FlexFiles.inc erbij
*/
%><!-- #include file="../shared/FlexFiles.inc" -->
<%

// "jquery-ui.js" en "date.js" zijn alleen nodig als er een datumkenmerk is.
// meestal is die er toch al wel in het hoofdscherm dus voor het gemak doen
// we het hier ook maar voor overige schermen.
if (!this.JSON_Result)
  FCLTHeader.Requires({plugins:["jQuery", "kenmerk"], js: ["jquery-ui.js", "date.js"]})

var requiredGroupUsed = new Array(); // Array zorgt voor eenmalige initiele aanroep van checkRequiredGroup(groep) per groep

function parseDocumentPath(kenmerk_val)
{
    // kenmerk_val is een padnaam naar een document
    // Als het document begint met <S("image_path_local")> dan halen we
    // dat er af voordat het de database in gaat.
    // We maken van back-slashes (alleen) dan altijd forward slashes
    // Bij presenteren plakken we er mettertijd S("image_path_virtual") voor
    // de check S("image_path_local") != "\\" is nog historisch om problemen te voorkomen
    var localpath = S("image_path_local").replace(/\//g,"\\");
    var newval = String(kenmerk_val).replace(/\//g,"\\");
    __Log("newval: " + newval);
    if (S("image_path_local") != "\\" && newval.indexOf(localpath) == 0)
        return newval.substring(localpath.length);
    else if (S("image_path_local_regexp") && newval.match(S("image_path_local_regexp")))
    {
        __Log("regexp match!");
        return newval.substring(RegExp.lastIndex);
    }
    else
        return kenmerk_val; // Origineel ongewijzigd
}

function kenmerk_write(s)
{
  Response.Write(s);
}

// Tel/ zoek de bijlagen in een folder
// Resultaat: array met bijlage namen
function BijlagenList(pModule, pNiveau, pKey, pKenmerk_key, otherKenmerk_path)
{
    if (otherKenmerk_path)
        var vAttachPath = S("flexFilesPath") + "/" + otherKenmerk_path + "/";
    else
    {
        if (pKey > 0) // anders valt er niets te tellen
            var vAttachPath = flexProps(pModule, pKey, String(pKenmerk_key), pNiveau).AttachPath + "/";
    }

    var lijst = [];

    var fso = Server.CreateObject("Scripting.FileSystemObject")
    if (vAttachPath)
        __Log("Tellen in folder: " + vAttachPath);
    if (vAttachPath && fso.FolderExists(vAttachPath))
    {
      var fso, f, f1, fc, s;
      fso = new ActiveXObject("Scripting.FileSystemObject");
      f = fso.GetFolder(vAttachPath);
      fc = new Enumerator(f.files);

      for (; !fc.atEnd(); fc.moveNext())
      {
        lijst.push(fc.item().Name);
      }
    }
    return lijst;
}

function BijlagenButton(pModule, pNiveau, pKey, tmpfolder, pKenmerk_key,
                        pReadonly, pRequired, kdim, pregexp, otherKenmerk_path, identify, label, params)
{
    // Lever code op voor een BijlagenButton type M (map)
    // Let op: F (file) gebeurt verderop in de code
    // Module is 'R' voor ruimte, 'M' voor melding enz
    // key is de object key
    // Parameter otherKenmerk_path is null of bevat het pad van het kenmerk waarvan we
    //           in een veel later stadium (SaveFlexKenmerken) de folder kopieren (order onder melding)
    //           of moven (wijzigen type stdmelding of type opdracht)
    //           Als die is meegegeven en er zitten bestanden in de folder dan wordt de knop
    //           vooralsnog read-only
    /* Bestanden zijn nu te vinden in ...cust/X/flexfiles/[module]/[key]/[kenmerk_key]  */
    var lijst = BijlagenList(pModule, pNiveau, pKey, pKenmerk_key, otherKenmerk_path);

    if (pReadonly)
    {   // Indien readonly en lijst.length = 0 dan hoeft het kenmerk (net als bij andere kenmerken) helemaal niet getoond te worden.
        var filehtmlarr = [];
        for (var file in lijst)
        {
            if (kdim == 'xxx')
                var href = protectQS.create(OpenFlexFile(pModule, pNiveau, pKey, pKenmerk_key, lijst[file], { mime_type: "image/png", contentdp: "inline" }));
            else
                var href = protectQS.create(OpenFlexFile(pModule, pNiveau, pKey, pKenmerk_key, lijst[file]));
            //var html = "<a href='" + safe.htmlattr(href) + "' target='_new'>" + safe.html(lijst[file]) + "</a>";
            var html = '<span class="details fldflexM" " readonly=1'
                     + ' onclick=\'window.open("' + safe.jsstring(href) + '")\'>' + safe.html(lijst[file]) + '</span>';

           filehtmlarr.push(html);
        }
        var res = filehtmlarr.join("</br>");;
    }
    else
    {
      if (otherKenmerk_path && lijst.length > 0)
        var res = L("lcl_flex_autocopy");
      else  // pf: op 1 regel tbv mijn syntaxhiliting
      {
        var res ="<input type='button' isBijlage='1' nBijlagen='" + lijst.length + "'" + identify + " class='bijlage button " + pRequired + "' value='" + label.format(lijst.length) + "'";
        var url = "../shared/BijlagenForm.asp?module=" + pModule
                                           + "&key=" + pKey
                                           + (pNiveau != ""? "&niveau=" + pNiveau : "")
                                           + "&kenmerk_key=" + pKenmerk_key
                                           + (params.kenmerk_module != ""? "&kenmerk_module=" + params.kenmerk_module : "");
        if (pKey < 0)
        {
          url += "&tmpfolder=" + tmpfolder;
        }

        if (pReadonly) url += "&reado=1";
        url += "&multi=1";
        if (kdim) url += "&extFilter=" + escape(kdim);
        if (pregexp) url += "&pregexp=" + escape(pregexp); // P800x600 voor foto van 800x600
        if (params.kenmerk_module != "")
          url += "&kenmerk_module=" + params.kenmerk_module;
        var blg = "onBijlagen('" + safe.jsstring(protectQS.create(url)) + "'"
             + ",''" // geen saveUrl
             + ", true" // multi
             + ", this)"
        res += " onClick='" + safe.htmlattr(blg) + "'"
             + (!pReadonly ? " onChange='checkKenmerk(this, false,\"M\",0,0,0,0)'" : "")
        res += ">";
        res+="<i class='fa fa-folder-open fa-fw hasIcon bijlage'></i>";
      }
    }
    return {html: res, count: lijst.length};
}

// Params kan bevatten
//  mime_type: "image/png"
//  contentdp: "attachment"
function OpenFlexFile(Module, Niveau, Key, Kenmerk_key, fname, params)
{
  params = params || {}
  // Lever code op voor tonen van een bepaalde flexfile streaming
  var s = "../shared/BijlagenStream.asp"
  s += "?module=" + Module
    +  "&key=" + Key
    + (Niveau? "&niveau=" + Niveau : "")
    +  "&kenmerk_key=" + Kenmerk_key
    +  "&filename=" + Server.URLencode(fname);
  if (params.mime_type)
    s += "&mime=" + params.mime_type;
  if (params.contentdp)
    s += "&contentdp=" + params.contentdp;
  return s;
}

function Operand( n)
{
    ret_str = "<select class='operand' id='ko" + n + "' name='ko" + n + "'>"
                  +"<option value='='>=</option> "
                  +"<option value='&lt;&gt;'>&lt;&gt;</option> "
                  +"<option value='&lt;'>&lt;</option>"
                  +"<option value='&gt;'>&gt;</option>"
                  +"<option value='&lt;='>&lt;=</option>"
                  +"<option value='&gt;='>&gt;=</option>"
                  +"<option value='LIKE'>"+L("lcl_operand_like")+"</option>"
                  +"<option value='NOT LIKE'>"+L("lcl_operand_notlike")+"</option>"
                  +"<option value='IS NOT NULL'>"+L("lcl_operand_notnull")+"</option>"
                  +"<option value='IS NULL'>"+L("lcl_operand_null")+"</option>"
                  +"</select>";
     return ret_str;
}

function getDatatypeValue(table, column, checkval)
{
    var tsql = "SELECT data_type"
                  + ", data_length"
                  + ", data_precision"
                  + ", data_scale"
             + "  FROM user_tab_columns"
             + " WHERE table_name = "  + safe.quoted_sql_upper(table)
             + "   AND column_name = " + safe.quoted_sql_upper(column);
    var oRs_t = Oracle.Execute(tsql);
    if (oRs_t("data_type").value == "NUMBER")
    {
        if (!oRs_t("data_scale").value || oRs_t("data_scale").value > 0)
            suggestval = parseFloat(checkval) || 0;
        else
            suggestval = parseInt(checkval) || 0;
    }
    else if (oRs_t("data_type").value == "VARCHAR2")
    {
        suggestval = safe.quoted_sql(checkval);
    }
    else
    {
        suggestval = safe.quoted_sql(checkval);
    }
    oRs_t.close();
    return suggestval;
}

// ================================================================================================
// listKenmerk
// Genereer code voor flexkenmerken
//
// Genereer code voor flexkenmerken. Vrij kale code
// <tr><td>lbl1:</td><td>flex1</td><td>lbl2:</td><td>flex2</td></tr> en dat dan herhaald
// (Alleen!) voor bewerkbare velden voegen we ook name's toe die met het formulier gesubmit worden
// params: sql:  moet een query met de goede :-) velden bevatten
//               sinds kort worden deze By Name gebruikt ipv By volgorde
//         module, key: alleen gebruikt voor flexkenmerk 'F' (file) 'E' (encrypted file) en 'M' (map)
//         props { fnIsInvisible:  optioneel function (volgnr, type) - true betekent onzichtbaar
//                 fnIsReadonly: optioneel function (volgnr)         - true betekent readonly
//                 parentKey: optioneel om flexkenmerken onder op te slaan
//                 kenmerk_module: optioneel bij ALG: "L", "G", "R" etc.
//                 multiMode
//                 hideVervallen: om alleen nu nog geldige waarden te tonen
//                 defaults { bsn: "12345678", plaats: "Enschede", postcode:"1234AB" } altijd lowercase namen
//                 prs_key (of obsolete reqId) - key om bij defaultwaarde in ##PRS_PERSLID_KEY## te substitueren
//                 fnpre en fnpost: functies die voor en na genereren worden aangroepen *mits er kenmerken zijn*
//                 wfbuilder: toon ook flex-colname :f123 (voor workflow expression builder)
//               }
// ================================================================================================
function listKenmerk(sql, module, key, props)
{
    var flexcolumns     = props.flexcolumns;
    var kenmerk_search  = props.kenmerk_search;
    var kenmerk_colspan = props.kenmerk_colspan;
    var kenmerk_extraTD = props.kenmerk_extraTD;
    var multiMode       = props.multiMode;   // TODO: support this!
    var hideVervallen   = props.hideVervallen;
    var prs_key         = props.prs_key || props.reqId;       // Oude obsolete reqId nog even ondersteunen
    var cont_key        = props.cont_key;
    var nameprefix      = props.nameprefix || "k";
    var extraserie      = props.extraserie || false;
    var niveau          = props.niveau || "";
    var srtdeel_key     = props.srtdeelKey;
    var requiredbyfield = props.requiredbyfield;
    var serie           = props.serie || 0;
    var parent_list     = props.parent_list; // Maak voor deze keys ook bijlagen knop
    var formobile       = props.mobile;
    var notr            = props.notr; // Geen tr's toevoegen.
    var nolabel         = props.nolabel; // Geen labels toevoegen.
    var flexstart       = props.flexstart || 1; // De kenmerken vanaf de flexstart-ste element tonen {flexstart >= 1 indien meegegeven).
    var flexend         = props.flexend || -1; // De kenmerken tot het flexend-ste element tonen {flexend >= 1 indien meegegeven}.
    var hasfilter       = props.hasfilter || false; // De kenmerken kunnen op naam gefilterd worden.
    var tmpfolder       = props.tmpfolder || "";

    var predone = false;

    var colCount      = 0;          // Even/oneven zodat we weten of we links of rechts zitten
    var forceNewLine  = false;      // Bij labels en grote text velden
    var val_seperator = '';
    var val_label     = '';
    var idCounter     = 1;          // We maken alleen name/id-s voor de velden die ook echt bewerkt kunnen worden
    var clabelCounter = 0;          // Het aantal collapsable labels
    var anyDate = false;
    var anyTextarea = false;
    var kenmerkidref = new Array(); // Bijhouden welk kenmerk welke id referentie mee heeft gekregen
    var dpYearRanges = [];          // te gebruiker jaren voor datepicker vastleggen.

    var allCollapsable = false;     // true bij eerste 'l'-label

    var kenmerk_2col = (flexcolumns >= 2); // 2 kolommnen voor flexkenmerken als aantal kolommen ingevuld 2 of meer is.

    if (module == "INS" && niveau == "S")
    {   // Sleutelmodule werkt met srtdeel_key
        if (srtdeel_key > 0)
            key = srtdeel_key;
    }

    // Zitten er expressie kenmerken tussen?
    var flexExprIncluded = false;
    var oRs = Oracle.Execute(sql);
    while (!oRs.eof)
    {
      var kdefault = oRs("kenmerk_default").Value;
      if (kdefault && kdefault.match('##EXPR##') != null)
      {
        flexExprIncluded = true;
        break;
      }
      oRs.MoveNext();
    }
    oRs.Close();

    // Opnieuw dezelfde query is niet supperefficient maar een MoveFirst is problematisch (FSN#39008)
    var oRs = Oracle.Execute(sql);

    // ============================================================================================
    // Doorloop alle flexkenmerken
    if (key < 0 && tmpfolder == "")
      tmpfolder = shared.random(32); // voor als nog geen key en wel bijlagen

    var prssql = false;
    for (i = 1; !oRs.eof; i++)
    {
      var val = null;
      val_seperator = ':';
      var def_val = oRs("kenmerk_default").Value;
      var isExpression = false;

      if (!kenmerk_search && !multiMode)
      {
        //  In multiMode no filling of default values
        val = oRs("kenmerk_waarde").value;

        if (def_val && def_val.match('##EXPR##') != null)
          isExpression = true;

        //  For a new value, this default is used. It could be a primitive value (string, number or key) or
        //  it is suggested to support (later)
        //  a more sophisticated value, using ##asp-variable## and/or SQL:SELECT in the string.
        if (val != null)
        { //__Log("val:"+val);
          if (val == def_val && val.match('##SQL##') != null)
          {
            //__Log("val match");
            prssql = true;
            val = val.substr(val.indexOf('##SQL##') + 7);
            var sql2 = val.replace("##PRS_PERSLID_KEY##", prs_key).replace("##PRS_CONTACTPERSOON_KEY##", cont_key);
            oRsPerslid = Oracle.Execute(sql2);
            if (!oRsPerslid.eof)
              val = oRsPerslid(0).value;
            else
              val = '';
            oRsPerslid.Close();
          }
          else if (def_val && val == def_val && def_val.match('##EXPR##') != null)
          {
            val = "";
          }
        }
      }
      // Meegegeven default verwerken (UWVA Pacta o.a.)
      if (props.defaults && oRs("kenmerk_omschrijving").value)
      {
        var xx = props.defaults[oRs("kenmerk_omschrijving").value.toLowerCase()];
        if (typeof xx != "undefined")
        {
            val = xx;
            if (oRs("kenmerk_kenmerktype").value == "C" || oRs("kenmerk_kenmerktype").value == "N" || oRs("kenmerk_kenmerktype").value == "B")
                val = val.substr(0, oRs("kenmerk_lengte").value);  // Kap de waarde op de maximale lengte af
        }
      }


      // De weer te geven waarde is nu bepaald
      var kkey    = oRs("kenmerk_key").value;
      val_label   = Server.HTMLEncode(oRs("kenmerk_omschrijving").value? oRs("kenmerk_omschrijving").value : ""); // Omschrijving kan leeg gelaten zijn
      var ktype   = oRs("kenmerk_kenmerktype").value;
      var klen    = oRs("kenmerk_lengte").value;
      if (!klen) klen = 4000;
      var kdec    = oRs("kenmerk_dec").value;
      var kmin    = oRs("kenmerk_nmin").value;
      var kmax    = oRs("kenmerk_nmax").value;
      var kvolgnr = "" + oRs("kenmerk_volgnr").value;
      var kdim    = oRs("kenmerk_dimensie").value; // beperkt bij uploads de extensie
      var hint    = oRs("kenmerk_hint").value;
      var kregexp = oRs("kenmerk_regexp").value;
      var kdomein_key = oRs("kenmerkdomein_key").value;

      // if (hint == null) hint = "";
      // PF vindt van niet if (def_val == null) def_val = "&nbsp;";
      var required = oRs("kenmerk_verplicht").value;
      if ((props.sqldefaultnotrequired && def_val && def_val.match('##SQL##')) || props.ignorerequired)
        required = false;

      var reqgroup = oRs("kenmerk_groep").value;
      var otherPath = oRs("otherpath").value; // otherKenmerkPath, alleen voor meldingen en facturen (tbv kopieren)
      //
      // forceReadonly kan gezet zijn: dan is het veld *altijd* readonly en kan alleen
      // ingevuld raken doordat een extern proces (importjob?) dat doet of het wordt
      // gevuld door de default waarde.
      var forceReadonly = oRs("kenmerk_toonbaar").value == 1;

      // Velden die de gebruiker niet mag zien zijn we héél snel klaar mee
      // Ook velden waar je niet op kunt zoeken slaan we direct over in zoekmode
      // En labels met een readonly-kenmerk tonen we niet als readonly, om invulinstructies in showmode weg te laten
      if ((!kenmerk_search && props.fnIsInvisible && props.fnIsInvisible(kvolgnr, ktype, oRs)) ||
            (kenmerk_search && (ktype == 'L' || ktype == 'l' || ktype == 'Q' || ktype == 'M' || ktype == 'F' || ktype == 'E') || kvolgnr < 0) ||
            (!kenmerk_search && (ktype == 'L' || ktype == 'l') && forceReadonly && props.fnIsReadonly(kvolgnr, oRs))
         )
      {
          oRs.MoveNext();
          continue; // Onzichtbaar
      }

      //kenmerk_write( "<!--YY:"+colCount+":"+val_label+"-->");
      //kenmerk_write( "<!--YY:"+idCounter+":"+val_label+"-->");

      var readonlyfield = forceReadonly; // startwaarde
      if (!readonlyfield && !kenmerk_search && props.fnIsReadonly) // Zoeken is natuurlijk nooit readonly
           readonlyfield = props.fnIsReadonly(kvolgnr, oRs)

      if (readonlyfield || kenmerk_search)
        required = false;

      var required_class = "";
      var required_group = "";
      if (required)
      {
        required_class = " required";
        // Indien required afhankelijk is van een gewoon veld dan requiredgroup gebruiken met groep >= 10
        if (requiredbyfield && required == 1)
        {
          required = 10;
        }

        if (required > 1)
        {
          if (required == 2)
            required_group = reqgroup + "S" + parseInt(serie, 10);
          else
            required_group = required + "S" + parseInt(serie, 10);

          required_class += required_group;
          if (!requiredGroupUsed[required_group]) // Array requiredGroupUsed[] zorgt voor eenmalige initiele aanroep van checkRequiredGroup(groep) per groep
          {
            requiredGroupUsed[required_group] = true;
%>          <td id="requiredgeg_<%=nameprefix + idCounter%>" style="display:none">
              <script type="text/javascript">
                jQuery(document).ready(function()
                                       {
<%                                       if (required > 9)
                                         { // Gewone veld ook dezelfde requiredgroup class geven
%>
                                           $("#<%=requiredbyfield%>").addClass("<%=required_class%>");
<%                                       } %>
                                         checkRequiredGroup("<%=required_group%>");
                                         $("#requiredgeg_<%=nameprefix + idCounter%>").remove(); // Na het uitvoeren kan de ready functie weer weg zodat deze niet
                                       });
              </script>
            </td>
<%        }
        }
      }

      // Als een parent kenmerk niet toonbaar is (vinkje "Niet te wijzigen" is gezet) en een lege waarde heeft,
      // dan moet het parent kenmerk toch getoond worden i.v.m. de afhankelijkheid.
      // en het child kenmerk is toonbaar (vinkje "Niet te wijzigen" is niet gezet)
      // Afhankelijke child-parent kenmerken kunnen niet beide "niet toonbaar" zijn.
      // Dan heeft de afhankelijkheid ook geen zin. Dus daarop ga ik niet controleren. Dat is een configuratie fout.
      // Alleen controle of parent niet toonbaar is bij een toonbare child.
      var isParent = false;
      if (kdomein_key)
      {
        sqlP = "SELECT ut2.fac_usrtab_parentkey"
             + "  FROM fac_kenmerkdomein kd1"
             + "     , fac_usrtab ut1"
             + "     , fac_usrtab ut2"
             + " WHERE kd1.fac_usrtab_key = ut1.fac_usrtab_key"
             + "   AND ut1.fac_usrtab_key = ut2.fac_usrtab_parentkey" // Kenmerk 1 is een parent van een ander kenmerk 2.
             + "   AND kd1.fac_kenmerkdomein_key = " + kdomein_key;
        var oRs2 = Oracle.Execute(sqlP);
        if (!oRs2.eof)
        {
          isParent = true; // Er is minstens 1 child kenmerk.
          // In zoekscherm hoeft de parent dan ook niet readonly te zijn. Je mag er mee zoeken
          if (kenmerk_search) readonlyfield = false;
        }
        oRs2.close();
      }

      // In het geval van readonly laten we velden zonder waarde helemaal niet zien,
      // met uitzondering van Labels.
      // Parent kenmerken laten we altijd zien.
      // PF vraagt zich af: heeft dit ook gevolg voor print en excel?
      // En: voor type M is het altijd nu nog leeg, en die komen dus readonly nooit!
      // Voorlopig laat ik die dan altijd maar zien (TODO: evt niet als er 0 bestanden zijn)
      if (readonlyfield && !isParent && ktype != 'L' && ktype != 'l'  && ktype != 'M' && (!val || val=="") &&
          (!kenmerk_search && (ktype == 'C' || ktype == 'N' || ktype == 'B' || ktype == 'D' || ktype == 'R' || ktype == 'S')) ) // Als geavanceerd dan maakt het niet uit of het readonly is. Dan moet je het kenmerk gewoon tonen.
      {
            oRs.MoveNext();
            continue;
      }

      // Als geavanceerd dan maakt het niet uit of het readonly is. Dan moet je het kenmerk gewoon tonen.
      if (kenmerk_search && readonlyfield && (ktype == 'C' || ktype == 'N' || ktype == 'B' || ktype == 'D' || ktype == 'R' || ktype == 'S'))
        readonlyfield = false;

      colCount++; // Nu pas ophogen.

      // De kenmerken vanaf de flexstart-ste element tonen.
      // De kenmerken tot het flexend-ste element tonen.
      if (flexstart > colCount || (flexend >= 0 && flexend < colCount))
      {
        oRs.MoveNext();
        idCounter++;
        continue;
      }

      if( ktype == 'L' || ktype == 'l' )
      {
        forceNewLine = true;
        colCount = 2; // Forceer 'even'
      }
      else
        forceNewLine = false;

      if (kenmerk_search || val && val!="")
        allCollapsable = false;

      // Eerst de simpele datatypes ('C', 'D' en 'N'). De complexere overrulen kv mogelijk later nog wel.
      // De classes-conventie is:
      //    fldflex+[ktype] voor edit/show en fldSflex+[ktype] voor searchmode
      // waarbij type niet perse hoeft als er geen onderscheid is.
      // Extra is er nog C50 ipv C voor lange C kenmerken
      // Ook class number/float (of currency) toevoegen

      var kv = "";           // Het in te voegen veld in HTML, zonder label
      var onchangeExp = "";

      // Een flexkenmerk wordt altijd weergegeven volgens het volgende format:
      // <tr><td class="label"><label>LABEL</label>:</td><td><input type="text" value='XX'></td></tr>
      // Eventueel met 2 kolommen in een rij.
      // Er zijn 2 uitzonderingen:
      // - een lange tekst wordt een textarea
      // - een R wordt een select
      // We doen eerst die uitzonderingen

      // KENMERKTYPE C (Karakter) =================================================================
      if (ktype == 'C')
      {
          if (flexExprIncluded && !isExpression)
            onchangeExp = ";if (valid) calculateFlexExpressions(this"
                                                 + ", \"" + module + "\""
                                                 + ", " + key
                                                 + ", \"" + (props.kenmerk_module || "") + "\""
                                                 + ", \"" + nameprefix + "\""
                                                 + ", \"" + extraserie + "\""
                                                 + ");";
          if (klen > 50 && !kenmerk_search)
          {
            // Groot text veld wordt automatisch textarea
            if (readonlyfield)
            {
                if (formobile)
                {
                    kv = "<textarea class='fldflexC50 ' rows=3 readonly tabindex=-1 maxlength='"+klen+"'>" + safe.textarea(val) + "</textarea>";
                }
                else
                {
                    kv = "<div class='readonly fldflexC50'>"
                        + safe.html(val)
                        + "</div>";
                }
            }
            else
            {
                anyTextarea = true; // straks autogrow code
                kv = "<textarea "
                    + (kregexp ? " regexp='" + kregexp + "' " : "")
                    + " onChange='valid=checkKenmerk(this, false,\"" + ktype + "\"," + klen + "," + kdec + "," + kmin + "," + kmax + ")"
                      + onchangeExp
                      + "'"
                    + " onBlur='checkKenmerk(this, true,\"" + ktype + "\"," + klen + "," + kdec + "," + kmin + "," + kmax + ")'"
                    + " class='fldflexC50 "
                      + (required ? required_class : "")
                      + (isExpression? " expression " : "")
                      + "'"
                    + (required ? " required='required' " : "")
                    + (isExpression ? " readonly " : "")
                    + " rows=3 "
                    + " maxlength='"+klen+"'"
                    + " name='" + nameprefix + idCounter + "val' id='" + nameprefix + idCounter + "val' "
                    //+ "placeholder='"+val_label+"'"
                    + ">"
                    + safe.textarea(val)
                    + "</textarea>";
            }
          } else { // klein input veld
            if (readonlyfield)
            {
                if (formobile)
                {
                    kv = "<input type='text' class='fldflexC ' readonly tabindex=-1 value='"+ safe.html(val) +"'>";
                }
                else
                {
                    kv = "<span"
                       +  " class='readonly "
                          + (kenmerk_search ? "fldSflexC " : "fldflexC ")
                       + "'>" + safe.htmlattr(val) + "</span>";
                }
            }
            else
            {
                kv = "<input type='text'"
                   + (kregexp ? " regexp='" + kregexp + "' " : "")
                   + " onChange='valid=checkKenmerk(this, false,\"" + ktype + "\"," + klen + "," + kdec + "," + kmin + "," + kmax + ")"
                     + onchangeExp
                     + "'"
                   + " onBlur='checkKenmerk(this, true,\"" + ktype + "\"," + klen + "," + kdec + "," + kmin + "," + kmax + ")'"
                   + " id='" + nameprefix + idCounter + "val' name='" + nameprefix + idCounter + "val'"
                    + " class='"
                      + (kenmerk_search ? "fldSflexC " : "fldflexC ")
                      + (required ? required_class : "")
                      + (isExpression? " expression " : "")
                      + "'"
                    + (required ? " required='required' " : "")
                    + (isExpression ? " readonly " : "")
                    //+ " placeholder='"+val_label+"'"
                    + " value='" + safe.htmlattr(val) + "'"
                    + " maxlength='" + klen + "'"
                    + ">";
            }
          }
      }  // C

      // Bijhouden welk kenmerk welke id referentie mee heeft gekregen
      function idref(idname, waarde)
      {
        this.idname = idname;
        this.waarde = waarde;
      }
      // Bijhouden van kenmerken. Later kan blijken dat ze parent kenmerken zijn.
      // Kenmerkkey alleen is niet uniek bij meerdere bezoekers of factuurregels.
      // Daarom nameprefix meenemen want die is per bezoeker of factuurregel wel uniek.
      kenmerkidref[nameprefix + kkey] = new idref(nameprefix + idCounter, val);
      var parentkenmerkid = "";
      var parentkenmerk_key = -1;
      var parentkenmerkwaarde;

      if (!readonlyfield && (ktype == 'S' || ktype == 'R'))
      {
        // Aannames die tot nu toe zijn gemaakt:
        // 1) Een kenmerk kan alleen afhankelijk zijn van een kenmerk van hetzelfde type.
        //    M.a.w. een suggest box kan nu nog alleen afhankelijk zijn van een andere suggestbox (idem geldt voor listbox).
        //    Als een suggestbox namelijk dezelfde kenmerkdomein gebruikt als een listbox en ze worden beide gelijktijdig gebruikt geeft dat anders problemen.
        // 2) Afhankelijke suggestboxen met hetzelfde kenmerkdomein mogen niet voorkomen. Anders kun je niet de juiste afhankelijke suggestbox bepalen.
        //    Idem geldt dit voor listboxen.
        // 3) Als een kenmerk afhankelijk is van een ander kenmerk dan moet de afhankelijke kenmerk een hoger volgnummer hebben (later worden getoond)
        //    dan het kenmerk waarvan het afhankelijk is.
        // 4) Afhankelijkheid van kenmerken werkt alleen voor eigen tabellen (fac_usrtab, fac_usrdata)

        // Is er een afhankelijkheid met een ander kenmerk (met een lager volgnummer)?
        var sqlDep = "SELECT tfin.kenmerk_key"
                   + "  FROM fac_usrtab ut1"
                   + "     , fac_usrtab ut2"
                   + "     , fac_kenmerkdomein kd1"
                   + "     , fac_kenmerkdomein kd2"
                   + "     , (" + sql + ") tfin"
                   + " WHERE kd1.fac_kenmerkdomein_key = " + kdomein_key
                   + " AND kd1.fac_usrtab_key = ut1.fac_usrtab_key"
                   + " AND ut1.fac_usrtab_parentkey = ut2.fac_usrtab_key"
                   + " AND ut2.fac_usrtab_key = kd2.fac_usrtab_key"
                   + " AND kd2.fac_kenmerkdomein_key = tfin.kenmerkdomein_key"
                   + " AND tfin.kenmerk_kenmerktype = " + safe.quoted_sql(ktype)
        var oRs2 = Oracle.Execute(sqlDep);
        if (!oRs2.eof)
        {  // Er is een afhankelijkheid met een ander kenmerk
          parentkenmerk_key = oRs2("kenmerk_key").value;
          parentkenmerkid = kenmerkidref[nameprefix + parentkenmerk_key].idname;
          parentkenmerkwaarde = kenmerkidref[nameprefix + parentkenmerk_key].waarde;
        }
        oRs2.close();
      }

      // KENMERKTYPE R/S (Referentie/Suggest) =====================================================
      if (ktype == 'R' || (readonlyfield && ktype == 'S')) // Referentie naar andere tabel (listboxje)
      {
        var wheres = [];
        var dsql = "SELECT fac_kenmerkdomein_objectnaam"
                 + ", fac_kenmerkdomein_kolomnaam"
                 + ", fac_kenmerkdomein_kolomtxt"
                 + ", fac_kenmerkdomein_xmlnode"
                 + ", fac_kenmerkdomein_verval"
                 + ", fac_usrtab_key"
                 + " FROM fac_kenmerkdomein"
                 + " WHERE fac_kenmerkdomein_key = " + kdomein_key;
        var oRs_d = Oracle.Execute(dsql);
        var xmlnode = oRs_d("fac_kenmerkdomein_xmlnode").value;
        var vervalkolom = oRs_d("fac_kenmerkdomein_verval").value;

        if (!readonlyfield && parentkenmerkid != "")
        {
          if (parentkenmerkwaarde != null)
            wheres.push("fac_usrdata_parentkey = " + parentkenmerkwaarde);
          else // Parent heeft geen waarde geselecteerd.
            wheres.push("fac_usrdata_parentkey IS NOT NULL");
        }

        if (val !== null && String(oRs_d("fac_kenmerkdomein_kolomnaam").value).match(/\_key$/i))
        {
            var vv = parseInt(val, 10);
            if (isNaN(vv))
            {   // Kan gebeuren als van een text-kenmerk *achteraf* een referentiekenmerk wordt gemaakt
                // Ook: als je bij een referentie kenmerk een foutieve default-waarde (textueel) invult
                __DoLog("Ongeldige referentie waarde <em>{0}</em> voor kolom {1}.{2}".format(val,
                                            oRs_d("fac_kenmerkdomein_objectnaam").value,
                                            oRs_d("fac_kenmerkdomein_kolomnaam").value), "#ff0000");
                val = "-1";
            }
            else
                val = String(vv);
        }

        if (oRs_d("fac_kenmerkdomein_objectnaam").value == "FAC_USRDATA" && oRs_d("fac_usrtab_key").value)
        {
          // Deze kunnen we standaard localisen!
          var fsql = "SELECT fac_usrdata_key, "
                   + lcl.xsql('fac_usrdata_omschr', 'fac_usrdata_key')
                   + " || CASE WHEN fac_usrdata_vervaldatum < SYSDATE THEN " + safe.quoted_sql(L("lcl_inactive_data_suffix")) + " END"
                     + " fac_usrdata_omschr"
                   + ", fac_usrdata_vervaldatum"
                   + "  FROM fac_usrdata ";
          wheres.push("fac_usrtab_key=" + oRs_d("fac_usrtab_key").value);

          if (!readonlyfield)
            wheres.push("fac_usrdata_verwijder IS NULL");  // bij readonly tonen we ook verwijderde oude waarden
          if (hideVervallen)
            wheres.push("(fac_usrdata_vervaldatum IS NULL OR fac_usrdata_vervaldatum > SYSDATE)");
        }
        else
        {
          var fsql = "SELECT " + oRs_d("fac_kenmerkdomein_kolomnaam").value
                   + "," + oRs_d("fac_kenmerkdomein_kolomtxt").value
                         + (vervalkolom
                           ? " || CASE WHEN  " + vervalkolom + " < SYSDATE THEN " + safe.quoted_sql(L("lcl_inactive_data_suffix")) + " END"
                           : ""
                           ) + " fac_kenmerkdomein_omschr"
                   + "," + (vervalkolom
                           ? vervalkolom
                           : "NULL"
                           ) + " x"
                   + "  FROM " + oRs_d("fac_kenmerkdomein_objectnaam").value;
          if ((!readonlyfield || hideVervallen) && vervalkolom)
            wheres.push("("+vervalkolom+" IS NULL OR "+vervalkolom+" > SYSDATE)");
        }

        if (readonlyfield)
          wheres.push(oRs_d("fac_kenmerkdomein_kolomnaam").value + " = " + safe.quoted_sql(val));
        if (wheres.length)
          fsql += " WHERE " + wheres.join(" AND ");

        fsql += (oRs_d("fac_kenmerkdomein_objectnaam").value == "FAC_USRDATA")
               ? " ORDER BY fac_usrdata_volgnr, 2"
               : " ORDER BY 2, 1";
        oRs_d.close();
        var oRs2 = Oracle.Execute(fsql);
        var c0 = "";
        var c1 = "";
        var inactiveval = false;
        if (!oRs2.eof)
        {
            c0 = oRs2(0); // for performance this is better, it seems (-JGL).
            c1 = oRs2(1);
            inactiveval = (oRs2(2).value != null && oRs2(2).value<new Date());
        }
        // Performance optimalisation: Instead of building the string by appending
        // we build it in an array first and finally 'join' it.
        var builder = new Array();
        var maxLen = 0;
        var kvv = "";

        while (!oRs2.eof)
        {
          if (readonlyfield)
          {
            if (c0.value == val)
            {
              kvv = c1.value;
              break; // Rap klaar. Kan efficienter met een WHERE?
            }
          }
          else
          {
            if (c1.value != null && String(c1.value.length) > maxLen)
              maxLen = String(c1.value).length;
            appender = '<option value="' + c0.value + '"'
                     + (c0.value == val?' selected' : '') + '>' + safe.html(c1.value) + '</option>';
            builder[builder.length] = appender;
          }
          oRs2.MoveNext();
        }

        if (kvv == '') kvv += builder.join(' ');
        if (kvv == '') kvv = ' '; // Zodat readonly/print in ieder geval het label getoond wordt.
        oRs2.close();

        if (!readonlyfield)
        { // is dus impliciet ook type R
          if (flexExprIncluded && !isExpression)
            onchangeExp = "calculateFlexExpressions(this"
                                     + ", \"" + module + "\""
                                     + ", " + key
                                     + ", \"" + (props.kenmerk_module || "") + "\""
                                     + ", \"" + nameprefix + "\""
                                     + ", \"" + extraserie + "\""
                                     + ");";
          kv = "<select id='" + nameprefix + idCounter + "val' name='" + nameprefix + idCounter + "val'"
             + " class='"
               + (kenmerk_search ? "fldSflexR " : "fldflexR ")
               + (required ? required_class : "")
               + "'"
          //+ " placeholder='"+val_label+"'"
          + (required ? " required='required' " : "")
          + (required > 1
            ? " onChange='checkRequiredGroup(\"" + required_group + "\");" + onchangeExp + "'"
            : (flexExprIncluded && !isExpression? " onChange='" + onchangeExp + "'" : ""))
          + ">"
          + "<option value='-1' " + (required? " class='" + required_class + "'" : "") + "><!--lege waarde--></option>"
          + kvv
          + "</select>";
          if (required) // wrapper voor rode streepje, IE-only. Wat vind je Jos (ik vind het te lelijk in chrome/safari)
            kv = "<!--[if lt IE 8]><div id='req_" + nameprefix + idCounter + "val' class='fldflexR " + required_class + "'><![endif]-->" + kv + "<!--[if lt IE 8]></div><![endif]-->";
        }
        else
        {
          var showdetail = (formobile ? "" : (xmlnode? ' details' : '') );
          var clickdetail = (formobile ? "" : (xmlnode? ' onClick="xmlNodeDetails(' + val + ', \'' + xmlnode + '\')"' : '') );
          if (ktype == "R")
            kv = '<input class="fldflexR'
               + showdetail
               + (inactiveval? ' inactive' : '')
               + '" type="text" readonly tabindex=-1 value="' + safe.htmlattr(kvv) + '"'
               + clickdetail
               + '>';
          else
            kv = '<input class="fldflexS'
               + showdetail
               + (inactiveval ? ' inactive' : '')
               + '" type="text" readonly tabindex=-1 value="' + safe.htmlattr(kvv) + '"'
               + clickdetail
               + '>';
        }
        if (!readonlyfield && parentkenmerkid != "")
        { %>
          <script type="text/javascript">
            function onChangeRKenmerkParent<%=parentkenmerkid%>()
            { // Functie wordt aangeroepen door een eventuele parent waarvan deze listbox afhankelijk is
              var className = $("#<%=parentkenmerkid%>val")[0].className;
              if (className.match(/required\d/g) && className.match(/required\d/g).length > 0)
              { // Select hoort binnen een required groep.
                var group = $("#<%=parentkenmerkid%>val")[0].className.match(/required\d*S\d*/g)[0].substr(8);
                checkRequiredGroup(group);
              }

              $("#<%=nameprefix + idCounter%>val").
                load("../Shared/loadRKenmerk.asp",
                     { init_key: $("#<%=nameprefix + idCounter%>val").val(),
                       kdomein_key: <%=kdomein_key%>,
                       parent_key: $("#<%=parentkenmerkid%>val").val(),
                       required: <%=required? required : 0%>,
                       hideVervallen: <%=hideVervallen? 1 : 0%> });
            }

            jQuery(document).ready(function()
            {
              // Op de onChange van de parent moet deze listbox opnieuw gevuld worden.
              // onchange van de parent koppelen aan de functie die deze actie uitvoert.
              $("#<%=parentkenmerkid%>val")[0].onchange =  function() {onChangeRKenmerkParent<%=parentkenmerkid%>();}
            })
          </script>
<%      }
      }

      // KENMERKTYPE F/E (File) ===================================================================
      var MasF = false; // M behandelen als F?
      if ((ktype == 'F' || ktype == 'E' || ktype == 'M') && formobile)
      {
        var lijst = BijlagenList(module, niveau, key, kkey, otherPath); // die hebben we dan nodig
      }
      if (ktype == 'M' && formobile && !lijst.length)
          MasF = true; // Een M zonder files doen we als F zodat je tenminste één betand kunt toevoegen

      if (ktype == 'F' || ktype == 'E' || MasF) // single File of Encrypted
      {
        if (formobile && lijst.length)
          readonlyfield = true;

        // Bedoeling is: toon de filenaam in een inputfield.
        // In showmode/readonly kun je er op klikken als-ie bestaat, in editmode
        //  moet je de change button gebruiken om te wijzigen en is niet klikbaar.
        // De echte waarde bewaren we in een hidden veld. Die wordt dan ook gesubmit
        var fld = nameprefix + idCounter + "val";
        // Er is redundantie: de bestandsnaam zit in de database en staat in de folder
        // door bijvoorbeeld niet op Ok te drukken na vervanging van het bestand
        // kan er inconstistentie ontstaan. Lastig tegen te gaan dus maar detecteren en melden.
        var missing = false;
        if (val && ktype != 'M')
        {
          var fso = new ActiveXObject("Scripting.FileSystemObject");
          if (otherPath)
            var vAttachPath = S("flexFilesPath") + "/" + otherPath;
          else
          {
            // JGL: Zou dit niet moeten/ kunnen via bijvoorbeeld iets als flexProps(module, key, String(kkey), niveau).AttachPath + "/";
            var vAttachPath = S("flexFilesPath") + "/" + module + "/" + subfolderKey(bepaalNiveau(module, niveau), key) + "/" + kkey;
          }
          missing = !fso.FileExists(vAttachPath + "/" + val);
          if (missing)
            __Log(vAttachPath + "/" + val + " is missing");
        }

        // Mobile heeft een compleet eigen BijlagenForm.asp
        if (kdim == 'xxx')
          var url = "../shared/BijlagenSignature.asp"
        else if (formobile)
          var url = "../pda/BijlagenForm.asp"
        else
          var url = "../shared/BijlagenForm.asp"
        url += "?module=" + module
                 + "&key=" + key
                 + (niveau != ""? "&niveau=" + niveau : "")
                 + "&kenmerk_key=" + kkey
                 + "&mobile=" + (formobile ? 1 : 0)
                 + (props.kenmerk_module != ""? "&kenmerk_module=" + props.kenmerk_module : "");
        // nooit if (pReadonly) url += "&reado=1";
        // nooit url += "&multi=1";
        if (kdim) url += "&extFilter=" + escape(kdim);
        if (kregexp) url += "&pregexp=" + escape(kregexp); // P800x600 voor foto van 800x600
        if (ktype == "E") url += "&encrypt=1";
        if (key < 0)
          url += "&tmpfolder=" + tmpfolder;

        var saveUrl = "";
        if (props.parentKey > 0 && ktype != 'M')
        {
          // Voor de zekerheid slaan we wijziging van de bestandsnaam direct op.
          // op het filesysteem is het namelijk ook al 'gecommit'
          var saveUrl="../shared/AjaxSaveFlex.asp";
          if (module=="MLD" && niveau == "O")
              saveUrl += "?module=OPDR";
          else if (module=="FIN" && niveau == "R")
              saveUrl += "?module=FINR";
          else
              saveUrl += "?module="+module;
          saveUrl += "&parentKey="+props.parentKey;
          saveUrl += "&kenmerkKey=" + kkey;
          if (props.kenmerk_module!="")
            saveUrl += "&kenmerk_module="+props.kenmerk_module;
        };

        var func = "onBijlagen";
        if (formobile)
          func = "onBijlagenMobile";

        var blg = func + "('" + safe.jsstring(protectQS.create(url)) + "'"
                + ",'" + (saveUrl?safe.jsstring(protectQS.create(saveUrl)):"") + "'"
                + ", false"
                + ", $('#" + fld + "')[0]"
                + ")";

        if (!readonlyfield || val)
        {
          kv = "<input type='text'"
             + (readonlyfield
               ? " readonly tabindex=-1 "
               : " id='" + fld + "' name='" + fld + "' ")
             + "class='"
               + (kenmerk_search
                 ? "fldSflex" + ktype + " "
                 : "fldflex" + ktype + " ")
               + (missing ? " missing " : " details ")
               + "'"
             + (required ? " required='required' " : "")
             + (required > 1
               ? " onChange='checkRequiredGroup(\"" + required_group + "\")'"
               : "")
             //+ " placeholder='"+val_label+"'"
             + " value='" + safe.htmlattr(val) + "' "
             + (!val?" style=display:none":"");
             //    + ">";  //komt later

          var kvClick="";
          if (otherPath) // Even niet klikbaar
            kvClick = " title='" + L("lcl_flex_autocopy") +"' ";
          else if (readonlyfield && val && !missing) // rechtstreeks openen
          {
            if (kdim == 'xxx')
                var fnclick = "parent.window.open('" + protectQS.create(OpenFlexFile(module, niveau, key, kkey, val, { mime_type: "image/png", contentdp: "inline" })) + "')";
            else
                var fnclick = "parent.window.open('" + protectQS.create(OpenFlexFile(module, niveau, key, kkey, val)) + "')";
            kvClick = " onclick='" + safe.htmlattr(fnclick) + "'";
          }
          else if (readonlyfield && val && missing) // Waarschuwen
            kvClick = " title='" + safe.htmlattr(L("lcl_flex_filenotfoundRO")) + "'";
          else
          {
            kvClick = " onClick='" + safe.htmlattr(blg) + "'"; // klik altijd naar bijlagenform
            if (missing)
            {
              kvClick += " title='" + safe.htmlattr(L("lcl_flex_filenotfound")) + "'";
            }
            else
              kvClick += " title='" + L("lcl_change") + "'";
          }
          // Om te saven doet de gebruiker maar rightclick/Saveas
          kv += kvClick + ">";  // hier tag afsluiten
        }

        if (!readonlyfield && !otherPath) // Altijd aanwezig
        {
          kv += "<input type='button' "
                    + "class='bijlage1 button " + (required? required_class : "") + "' "
                    + "isBijlage='1' nBijlagen='"+(val?1:0)+"' "
                    + "value='" + L("lcl_add") + "'"
                    + (val?" style='display:none' ": " ")
                    + "onClick=\"" + blg + "\""
                    +">";
        }
        if (kdim == 'xxx')
        {
          if (!missing && val)
            kv += "<img class='kenmerk' src=' " + protectQS.create(OpenFlexFile(module, niveau, key, kkey, val, { mime_type: "image/png", contentdp: "inline" })) + "'>";
          else
            kv += "<img class='kenmerk' style='display:none'>";
        }
      }

      // KENMERKTYPE M (Map) ======================================================================
      // Mobiel hebben we hem al bij 'F' gedaan
      if (ktype == 'M' && !MasF) // Map met bestanden is altijd heel simpel
      {
        var requiredfield = (required ? required_class : "");
        var identify = " id='" + nameprefix + idCounter + "val' name='" + nameprefix + idCounter + "val'";

        var res = BijlagenButton(module, niveau, key, tmpfolder, kkey, readonlyfield || formobile, // Mobile altijd readonly
                                     requiredfield, kdim, kregexp, otherPath, identify, L("lcl_appendixes_teller"), props)
        kv = res.html;
        if (res.count > 0)
            allCollapsable = false;
        if (parent_list)
        {
            for (pp in parent_list)
            {   // JGL: parent_list is alleen bij workflowmeldingen in *show*mode.
                //      Meeste parameters hieronder zullen dan ook don't care zijn
                var res = BijlagenButton(parent_list[pp].module, niveau, parent_list[pp].key,
                                         tmpfolder, kkey, readonlyfield, requiredfield, kdim, kregexp, otherPath, identify, parent_list[pp].label, props)
                if (res.count > 0)
                {
                    allCollapsable = false;
                    kv = res.html + "<br/>" + kv; // parent komt ervoor
                }
            }
        }
      }

      var makeCheckbox = false;
      if ((ktype == 'N' || ktype == 'B') && klen == 1 && kmin == 0 && kmax == 1 && (kdec == 0 || !kdec))
      {
        makeCheckbox = true;
        if (formobile) // Daar staat de tekst achter het vinkje
            val_seperator = '';
      }

      // ===========================================================================
      // De uitzonderingen hebben we nu gehad, alle overige types beginnen hetzelfde
      // ===========================================================================
      // JGL: Wat zijn de overige types nog van CNDRSXFEMLlQB ?
      //      Volgens mij NDXLlQB waarbij L en l nog overruled worden.

      if (kv == "" && ktype != "M" && ktype != "F" && ktype != "E" && !makeCheckbox)
      { // Ook lege folders met bestanden en een enkele bestandsnaam krijgen we via de query mee. Maar deze niet tonen.
        // Ooit meer HTML-types toepassen (number, float)
        if (flexExprIncluded && (ktype == "N" || ktype == "D" || ktype == "S") && !isExpression)
          onchangeExp = ";if (valid) calculateFlexExpressions(this"
                                               + ", \"" + module + "\""
                                               + ", " + key
                                               + ", \"" + (props.kenmerk_module || "") + "\""
                                               + ", \"" + nameprefix + "\""
                                               + ", \"" + extraserie + "\""
                                               + ");";
        kv = "<input type='text'"
           + (!readonlyfield && kregexp ? " regexp='" + kregexp + "' " : "")
           + (!readonlyfield
             ? " onChange='valid=checkKenmerk(this, false,\"" + ktype + "\"," + klen + "," + kdec + "," + kmin + "," + kmax + ")"
               + onchangeExp
               + "' "
             : "")
           + (!readonlyfield ? " onBlur='checkKenmerk(this, true,\"" + ktype + "\"," + klen + "," + kdec + "," + kmin + "," + kmax + ")' " : "")
           + (readonlyfield? " readonly tabindex=-1 "
                : " id='" + nameprefix + idCounter + "val' name='" + nameprefix + idCounter + "val' ");
      }

      //
      // LET OP: kv is nu een "OPEN" variabele, die nog minstens een sluithaak nodig heeft!
      //

      // KENMERKTYPE N (Numeriek) of B (Berekening) ===============================================
      if (ktype == 'N' || ktype == 'B')
      { // Checkbox of numeriek/berekening input veld.
        if (makeCheckbox) // deze doen we compleet hier
        {
          if (readonlyfield || ktype == 'B') // Berekening input veld is altijd readonly.
          {
            if (formobile)
            {
                kv = "<input type='text' class='fldflexc ' readonly tabindex=-1 value='" + (val == 1? L("lcl_check_1") : L("lcl_check_0")) + "'>";
            }
            else
            {
                kv = "<span"
                   +  " class='readonly "
                      + (kenmerk_search ? "fldSflexC" : "fldflexC") // Is nu tekstveld
                    + "'>" + (val == 1? L("lcl_check_1") : L("lcl_check_0")) + "</span>";
            }
          }
          else
            kv = "<input type='checkbox' class='"
                + (kenmerk_search
                  ? "fldCSflexN "
                  : "fldCflexN ")
                + (required ? required_class : "")
                + "'"
                + " onChange='checkKenmerk(this, false,\"" + ktype + "\"," + klen + "," + kdec + "," + kmin + "," + kmax + ")' "
                + " onBlur='checkKenmerk(this, true,\"" + ktype + "\"," + klen + "," + kdec + "," + kmin + "," + kmax + ")' "
                + (val == 1
                  ? " checked"
                  : "")
                + " id='" + nameprefix + idCounter + "val' name='" + nameprefix + idCounter + "val' "
                + ">"
                + "<input type='hidden' id='has_" + nameprefix + idCounter + "val' name='has_" + nameprefix + idCounter + "val' value='1'>";
        }
        else
          kv += " class='"
                + (kenmerk_search
                  ? "fldSflexN "
                  : "fldflexN ")
                + (required ? required_class : "")
                + (kdec > 0 ? " float " : " number ")
                + (isExpression? " expression " : "")
                + "'"
              + (required ? " required='required' " : "")
              + (isExpression ? " readonly " : "")
              //+ " placeholder='"+val_label+"'"
              + " value='" + (kdec > 0 && val? safe.displayfloat(parseFloat(val), kdec) : safe.html(val)) + "'"
              + " maxlength='" + ((kmin >= 0? 0 : 1) + (kdec > 0? 1 : 0) + klen) + "'"
              + ">";
      }

      // KENMERKTYPE D (Date) =================================================================
      if (ktype == 'D')
      {
         kv += " class='"
              + (kenmerk_search
                ? "fldSflexD "
                : "fldflexD ")
              + (required ? required_class : "")
              + (isExpression? " expression " : "")
              + "'"
             + (required ? " required='required' " : "")
             + (isExpression? " readonly " : "")
             + " value='" + safe.htmlattr(val) + "'"
            + ">";

          if (!readonlyfield && !isExpression)
            kv += "<i class=\"fa fa-calendar fa-fw dateklikker\" onclick=\"$('#"
                + nameprefix + idCounter + "val').datepicker('show')\"></i>";
         // Als er voor een datumveld geen kmin en kmax is ingevuld dan is de yearRange default -10 tot +10 jaar vanaf geselecteerd jaar.
         // Is een van beiden ingevuld dan wordt de andere op 10 jaar vanaf geselecteerde jaar gezet (deze verschuift met selectie).
         // Een opgegeven waarde is het aantal jaren voor of na het huidige jaar (waarde < 1000), of een jaartal (waarde > 1000).
         if (kmin || kmax)
         {
           range_val = (kmin ? ((Math.abs(kmin)<1000) ? "c"+(kmin>0?"+"+kmin:kmin) : Math.abs(kmin)) : "c-10") + ":"
                     + (kmax ? ((Math.abs(kmax)<1000) ? "c"+(kmax>0?"+"+kmax:kmax) : Math.abs(kmax)) : "c+10");
           field_id = nameprefix + idCounter + "val";
           dpYearRanges.push({id: field_id, range: range_val});
         }
        anyDate |= !readonlyfield;
      }


      // KENMERKTYPE X (Bestandsnaam/url) =========================================================
      if (ktype == 'X')
      {
          if (S("flex_X_is_URL")==0)
            var viewpath = S("image_path_virtual") + val;
          else
            var viewpath = val;

          if (readonlyfield) // Je mag er wel op klikken
            if (val)
               kv = '<input class="details fldflexX" onclick=\'openDoc("' + safe.jsstring(viewpath) + '")\' value="' + safe.htmlattr(val) + '">';
            else
               kv = "";
          else if (val) // Tekstveld met 'view' button er achter plakken
            kv += " class='fldflexX"
                 + (required ? required_class : "")
                 + "'"
                + " value='" + safe.htmlattr(val) + "'"
                + (required ? " required='required' " : "")
                + "><input type='button' class='fileopen details' "
                + " onClick='openDoc(\"" + safe.jsstring(viewpath) + "\");'>";
          else if (!kenmerk_search) // !val // Browse button for new er achter plakken
            kv = "<input class='fldflexX"
                 + (required ? required_class : "")
                 + "'"
               + (required ? " required='required' " : "")
               + (required > 1
                 ? " onChange='checkRequiredGroup(\"" + required_group + "\")'"
                 : "")
               + " type='"
               + (S("flex_X_is_URL") == 0
                 ? 'file'
                 : 'text')
               + "' "
               + ((S("flex_X_is_URL")==0) && kregexp
                 ? " regexp='" + kregexp + "' "
                 : "")
               + "name='" + nameprefix + idCounter + "val' "
               + "value='" + safe.htmlattr(val) + "'"
               + ">";
          else
            kv += ">";
      }


      // KENMERKTYPE S (Suggest) ==================================================================
      // readonlyfield is bij "R" al meegenomen
      if (ktype == 'S' && !readonlyfield) // Referentie naar andere tabel (suggest box)
      {
        // Huidige waarde opzoeken
        var dsql = "SELECT fac_kenmerkdomein_objectnaam "
                 + ", fac_kenmerkdomein_kolomnaam "
                 + ", fac_kenmerkdomein_kolomtxt "
                 + ", fac_usrtab_key "
                 + "FROM fac_kenmerkdomein "
                 + "WHERE fac_kenmerkdomein_key = " + kdomein_key;
        var oRs_d = Oracle.Execute(dsql);

        var suggestval = getDatatypeValue(oRs_d("fac_kenmerkdomein_objectnaam").value, oRs_d("fac_kenmerkdomein_kolomnaam").value, val);
        if (oRs_d("fac_kenmerkdomein_objectnaam").value == "FAC_USRDATA"  && oRs_d("fac_usrtab_key").value)
        {
            var fsql = "SELECT fac_usrdata_key, "
                     + lcl.xsql('fac_usrdata_omschr', 'fac_usrdata_key')
                     + " || CASE WHEN fac_usrdata_vervaldatum < SYSDATE THEN " + safe.quoted_sql(L("lcl_inactive_data_suffix")) + " END"
                       + " fac_usrdata_omschr"
                     + ", fac_usrdata_vervaldatum"
                     + "  FROM fac_usrdata "
                     + " WHERE " + oRs_d("fac_kenmerkdomein_kolomnaam").value + " = " + suggestval;
            fsql += "  AND fac_usrtab_key = " + oRs_d("fac_usrtab_key").value
                  + "  AND fac_usrdata_verwijder IS NULL"
        } else {
            var fsql = "SELECT " + oRs_d("fac_kenmerkdomein_kolomnaam").value
                     + "," + oRs_d("fac_kenmerkdomein_kolomtxt").value
                     + ", NULL x"
                     + "  FROM " + oRs_d("fac_kenmerkdomein_objectnaam").value
                     + " WHERE " + oRs_d("fac_kenmerkdomein_kolomnaam").value + " = " + suggestval;
        }
        oRs_d.close();

        var oRs2 = Oracle.Execute(fsql);
        var currval = "";
        //var inactiveval = false;
        if (!oRs2.eof)
        {
            currval = oRs2(1).value;
            //inactiveval = (oRs2(2).value != null);  // zie opmerking hieronder
        }
        oRs2.close();
%>      <script type="text/javascript">
<%        if (parentkenmerkid != "")
          { %>
            function onChangeSKenmerkParent<%=parentkenmerkid%>(parentkey, parenttxt)
            { // Functie wordt aangeroepen door een eventuele parent waarvan deze suggest afhankelijk is
              // setValue(key, txt, doonChange, checkExist, extraParam, lastTry)
              var presentkey = $("#<%=nameprefix + idCounter%>val").val();
              var presenttxt = $("#Suggest<%=nameprefix + idCounter%>_show").val()
              Suggest<%=nameprefix + idCounter%>.setValue(presentkey, presenttxt, false, true, null, true);
              // Indien suggestbox verplicht is en maar één mogelijke waarde heeft, dan deze selecteren
              if ($("#Suggest<%=nameprefix + idCounter%>_show").hasClass("required")) Suggest<%=nameprefix + idCounter%>.CheckJustOne();

              var className = $("#Suggest<%=parentkenmerkid%>_show")[0].className;
              if (className.match(/required\d/g) && className.match(/required\d/g).length > 0)
              { // Suggest hoort binnen een required groep.
                var group = $("#Suggest<%=parentkenmerkid%>_show")[0].className.match(/required\d*S\d*/g)[0].substr(8);
                checkRequiredGroup(group);
              }
            }
<%        } %>

<%        if (required)
          { %>
            function onChangeSKenmerk<%=nameprefix + idCounter%>(parentkey, txt)
            {
              checkRequiredGroup("<%=required_group%>");
            }
<%        } %>

          jQuery(document).ready(function()
          {
            if ($("#execDone<%=nameprefix + idCounter%>").val() == 0)
            { // Alleen initieel uitvoeren. Niet bij overzichten als "Geavanceerd" kenmerken worden gekopieerd naar div binnen form. Want waarde kan veranderd/ingevuld zijn.
              Suggest<%=nameprefix + idCounter%> = new Suggest({objectName: "Suggest<%=nameprefix + idCounter%>",
                                                                queryField: $("#Suggest<%=nameprefix + idCounter%>_show")[0],
                                                                queryUrl: "../shared/Suggest/SuggestKenmerkDomein.asp?kenmerkdomein_key=<%=kdomein_key%><%=(hideVervallen?'&filtcode=A':'')%>",
                                                                initKey: '<%=val%>',
                                                                keyField: $("#<%=nameprefix + idCounter%>val")[0],
                                                                fieldReadonly: <%=readonlyfield? "true" : "false"%>
<%                                                              if (parentkenmerkid != "")
                                                                { %>
                                                                  ,urlAdd: [{ urlParam: "parent_key", field: "<%=parentkenmerkid%>val" }]
<%                                                              } %>
                                                              });
<%            if (parentkenmerkid != "")
              { %>
                // Op de onChange van de parent moet deze suggestbox geleegd worden.
                // sgonchange van de parent koppelen aan de functie die deze actie uitvoert.
                $("#Suggest<%=parentkenmerkid%>_show").attr("sgonChange", "onChangeSKenmerkParent<%=parentkenmerkid%>");
<%            } %>
              $("#execDone<%=nameprefix + idCounter%>").val(1); // Het is nu 1 keer uitgevoerd. Nu niet meer uitvoeren.
            }
          })
        </script><%
        kv = "<input type='text' "
           + (kregexp ? " regexp='" + kregexp + "' " : "")
           + "id='Suggest" + nameprefix + idCounter + "_show' "
           + "name='Suggest" + nameprefix + idCounter + "_show' "
           + "value='" + safe.htmlattr(currval) + "' "
           + "class='" + (kenmerk_search ? "fldflexS" : "fldSflexS")
             + (required ? required_class : "") + "'"
           // zo wordt het veld rood, ongeacht de waarde, wat verwarrend is, dus niet + (inactiveval ? " inactive " : "")
           + (required ? " required='required' " : "")
           + (required > 1
             ? " sgonChange='onChangeSKenmerk" + nameprefix + idCounter + "'"
             : "")
           + ">"
           + "<input type='hidden' id='" + nameprefix + idCounter + "val' name='" + nameprefix + idCounter + "val'>"
           + "<input type='hidden' id='execDone" + nameprefix + idCounter + "' name='execDone" + nameprefix + idCounter + "' value='0'>";
      }

      // KENMERKTYPE L (Label) ====================================================================
      if (ktype == 'L' || ktype == 'l')
      { // Label line
        allCollapsable = (ktype == 'l'); // opnieuw beginnen met zoeken

        val_label = oRs("kenmerk_omschrijving").Value;  // was: 1
        clabelCounter++;
        val_seperator = '';
        kv = def_val;
         // suggestie voor UWVA#20007: if required then collapsable
      }

      // ==========================================================================================
      // Alles bepaald, nu positioneren enzo
      // ==========================================================================================
      if (!predone && props.fnpre && (kv || (ktype == 'L' || ktype == 'l')))
      {
          predone = true;
          props.fnpre();
      }
      // KENMERKTYPE Q (spacer) ===================================================================
      // Alleen echt nuttig bij 2-koloms layout
      if (ktype == 'Q')
      {
          // PF: deze implementatie lijkt me discutabel; is forceNewLine niet beter?
          if (((colCount&1) || !kenmerk_2col) && !formobile && !notr)
            kenmerk_write('<tr>');
          else if (!((colCount&1) && kenmerk_2col) && !formobile && !notr)
            kenmerk_write('</tr>');
          oRs.MoveNext();
          continue; // Teller is wel opgehoogd: volgende komt vanzelf op nieuwe regel
      }

      if (kv != '')
      {
        // Schrijf eventueel een tabelregel begin
        if (((colCount&1) || forceNewLine || !kenmerk_2col) && !formobile && !notr)
          kenmerk_write('<tr' + (ktype == 'L' || ktype == 'l'? ' id="'+nameprefix+'clabel' + clabelCounter + '"' : '')
                      + ' class="trlabel ' + (ktype == 'L' || ktype == 'l'? 'flexlabel' : '') + (ktype == 'l' && !readonlyfield? ' flexcollapsable' : '') + '"'
                      + (ktype == 'l'? ' onclick="$(\'#'+nameprefix+'clabel' + clabelCounter + '\').nextUntil($(\'#'+nameprefix+'clabel' + (clabelCounter + 1) + '\')).toggle();'
                                               + '$(this).toggleClass(\'flexcollapsed\');'
                                               + 'FcltMgr.resized(window);"'
                                     : '')
                      + '>'); // Nu altijd zichtbaar in popup dmodal scherm
        // Schrijf een tabelcell met het label

        if (makeCheckbox && formobile) // Zie commentaar iets verderop
            props.label_colspan = 2;

        if (props.wfbuilder) // Workflow Expression Builder
          kenmerk_write("<td>:flex{0}</td>".format(kkey));

        if (!formobile && !nolabel)
          kenmerk_write('<td class="label' + (ktype == 'L' || ktype == 'l'? ' flexlabel' : '')+ (kenmerk_search? ' flexsearch' : '') + '"'
                      + (props.label_colspan? ' colspan=' + props.label_colspan : '')
                      + '>');
        if (!nolabel)
          kenmerk_write('<label for="' + nameprefix + idCounter + 'val" class="label' + (ktype == 'L' || ktype == 'l'? ' flexlabellabel' : '')  + (isExpression ? ' expression' : '') + '">'
                      + '<span title="' + safe.htmlattr(hint) + '">'
                      + val_label + ((kdim != null && kdim != "xxx")? ' [' + kdim + ']' : '') + val_seperator + '</span></label>');

        // labelwaardes (vaak invulinstructie) in apart te stylen span
        if (formobile && (ktype == 'L' || ktype == 'l') && kv) {
            kv = '<span class="flexlabelvalue">' + kv + '</span>';
         }

        // Bij mobile willen we de checkbox en zijn label in dezelfde ene TD hebben met colspan=2.
        // jQuery voegt ze namelijk samen tot een enkel interface element en dat past niet altijd
        // in de ene TD anders
        if ((!makeCheckbox || !formobile) && !formobile)
        {
            if (!nolabel)
              kenmerk_write('</td>');
            kenmerk_write('<td ' + (ktype == 'L' || ktype == 'l'? ' class="flexlabel lhint"' : ''));

            if (kenmerk_search && (ktype != 'L' && ktype != 'l'))
            {
                kenmerk_write('>');
                kenmerk_write(Operand(idCounter));
            }
            else
            {
              if ( ktype == 'L' || ktype == 'l' )
                if (typeof kenmerk_extraTD != "undefined" && kenmerk_extraTD)
                  kenmerk_write(' colspan=4>'); // MLD_OPDR heeft TD-tje extra
                else
                  kenmerk_write(' colspan=3>');
              else
                kenmerk_write(">");
            }

            if (nolabel)
              kenmerk_write('<label for="' + nameprefix + idCounter + 'val">'
                          + '<span title="' + safe.htmlattr(hint) + '">'
                          + '</span></label>');
        }

        // Schrijf vervolgens nu het echte veld
        kenmerk_write(kv);

        if (typeof kenmerk_extraTD != "undefined" && kenmerk_extraTD && !formobile)
            kenmerk_write('</td><td>') // Extra leeg celletje voor layout MLD_OPDR

        if (val && forceReadonly && ktype != "M" && ktype != 'L' && ktype != 'l')
        {    // hidden veld met de *echt* waardes zodat ze wel worden opgeslagen
            kenmerk_write("<input type=hidden id='" + nameprefix + idCounter + "val' name='" + nameprefix + idCounter + "val' value='" + safe.htmlattr(val) + "'>")
        }

        if ((kenmerk_search || !readonlyfield || (val && forceReadonly)) && ktype != "M" && ktype != 'L' && ktype != 'l')
        { // Alleen dan hidden name-velden er bij
%>        <input type=hidden name='<%=nameprefix + idCounter%>key' id='<%=nameprefix + idCounter%>key' value='<%=kkey%>'>
          <input type=hidden name='<%=nameprefix + idCounter%>t' id='<%=nameprefix + idCounter%>t' value='<%=ktype%>'>
<%        if (isExpression)
          { %>
            <input type=hidden name='<%=nameprefix + idCounter%>e' id='<%=nameprefix + idCounter%>e' value='1'>
<%        } %>
<%      }
        idCounter++; // Nu ophogen

        if (!formobile)
        {
          kenmerk_write('</td>');
          if (!((colCount&1) && kenmerk_2col) && !notr)
              kenmerk_write('</tr>');
        }
      }
      oRs.MoveNext();

      // Collapse code er achteraan als de *volgende* een ander label is
      if (allCollapsable && (oRs.Eof || oRs("kenmerk_kenmerktype").value.toUpperCase() == 'L')) // einde huidige label
      {
        kenmerk_write('<script>$(document).ready(function(){$("#' + nameprefix + 'clabel' + clabelCounter + '").addClass("flexcollapsed").nextUntil($("#' + nameprefix + 'clabel' + (clabelCounter + 1) + '")).hide();})</script>');
        allCollapsable = false;
      }
    }
    oRs.close();
    if (allCollapsable) // allerlaatste ook nog
        kenmerk_write('<script>$(document).ready(function(){$("#' + nameprefix + 'clabel' + clabelCounter + '").addClass("flexcollapsed").nextUntil($("#' + nameprefix + 'clabel' + (clabelCounter + 1) + '")).hide();})</script>');

    var counterName = (extraserie? nameprefix + "_all" : "k_all");
    if (!formobile)
    {
      if (!notr)
        kenmerk_write('<tr>');
      kenmerk_write('<td>');
    }
    if (key < 0)
      kenmerk_write("<input type='hidden' name='ktmpfolder" + serie + "' id='ktmpfolder" + serie + "' value='" + tmpfolder + "'>");
    kenmerk_write('<input type="hidden" id="' + counterName + '" name="' + counterName + '" value="' + (idCounter - 1) + '">');
    kenmerk_write('<input type="hidden" id="k_flexsql" name="k_flexsql" value="' + (prssql? 1 : 0) + '">');
    if (!formobile)
    {
      kenmerk_write('</td>');
      if (!notr)
        kenmerk_write('</tr>');
    }

    if (predone && props.fnpost)
        props.fnpost();

    // Eventuele scripts moeten buiten de </table>
    if (anyTextarea && !kenmerk_search)
    {
        %><script type="text/javascript">
        jQuery(document).ready( function()
        {
           $('textarea').resize(function () { FcltMgr.resized(window) } );
           if ($.fn.autogrow) // Niet bij mobile
               $('textarea').autogrow();
        });
      </script><%

    }
    // Bij kenmerk_search doen de load_kenmerk's geen FCLTHeader.Generate en dus hebben
    // we date.js niet gegarandeerd. .fldSflexD werd echter hieronder toch al niet
    // meegenomen dus komen we er mee weg gewoon hier geen kalenders te doen
    if (anyDate && !kenmerk_search)
    {
        // FCLTHeader.Requires is hier te laat
        %><script type="text/javascript">
        // Zie ook calendar.inc. Delen code was iets te lastig
        jQuery(document).ready( function()
        {
            if ($.datepicker) // Is er niet mobile
            {
              $(".fldflexD").not("[readonly]").datepicker({
                      // buttonImage: "../pictures/calendar2.png",
                      // buttonImageOnly: true,
                      showOn: "none",
                      duration: 'fast',
                      dateFormat: 'dd-mm-yy', // zo verwacht de flex-code het.
                      changeYear: true,
                      changeMonth: true,
                      duration: 'fast',
                      beforeShow: function(input, inst) { setTimeout("FcltMgr.resized(window)", 100) },
                      dayNames: calendar_names.days,
                      dayNamesMin: calendar_names.daysMin,
                      dayNamesShort: calendar_names.daysMin,
                      firstDay: 1,
                      monthNamesShort: calendar_names.monthsShort,
                      monthNames: calendar_names.months,
                      showWeek: true
                  });
<%
              for (i=0; i < dpYearRanges.length; i++)
              {
                fld_id  = dpYearRanges[i].id;
                fld_val = dpYearRanges[i].range;
%>
                $("#<%=fld_id%>").datepicker("option", "yearRange", "<%=fld_val%>");
<%
              }
%>
            }
        });
      </script><%
    }

    if (hasfilter)
    {
%>
    <script>
        $(document).ready(function () {
                $("#insFlex2 .fcltblockhead").append("<input id='autofilter' type='text' placeholder='"+L("lcl_autofilter")+"'>");
                $("#autofilter")
                    .click(function(e) {e.stopPropagation()})
                    .keyup(function(e) {
                        if (e.which == 27) {
                            $("#autofilter").val("");
                        }
                        delayed(200, doFilter);
                    })
            });

        function delayed(delay, fn)
        {
            if (window.timerID)
                clearTimeout(window.timerID);
            window.timerID = setTimeout(fn, delay);
        }

        function doFilter(deze)
        {
            var deze = $("#autofilter");
            var valThis = deze.val().toLowerCase();
            $('#insFlex2List table.fcltblocktab>tbody>tr.trlabel').each(function() {
                var text = $(this).find("td.flexsearch>label>span").text().toLowerCase();
                $(this).toggle(text.indexOf(valThis) > -1);
            });
        }
    </script>
<%
    }
    if (props.getTmpfolder)
      return tmpfolder;
}


// Ondersteunende filehandlingfuncties ============================================================
// Hernoem folders onder basepath
// Flexsql levert twee velden op: oude key en nieuwe key
function RenameFlexFolders(basepath, flexsql)
{
    var basepath = S("flexFilesPath") + "/" + basepath + "/";
    var oRs = Oracle.Execute(flexsql);
    while (!oRs.Eof)
    {
        var vAttachPath = basepath + oRs(0).value;
        var vAttachPath2 = basepath + oRs(1).value;
        MoveFlexfiles(vAttachPath, vAttachPath2);
        oRs.MoveNext();
    }
}

// Kopieer folders van oldbasepath naar newbasepath
// Flexsql levert twee velden op: oude key en nieuwe key
function CopyFlexFolders(oldbasepath, newbasepath, flexsql)
{
    var fso = Server.CreateObject("Scripting.FileSystemObject")
    var oldbasepath = S("flexFilesPath") + "\\" + oldbasepath + "\\";
    var newbasepath = S("flexFilesPath") + "\\" + newbasepath + "\\";
    var oRs = Oracle.Execute(flexsql);
    while (!oRs.Eof)
    {
        vAttachPath = oldbasepath + oRs(0).value;
        vAttachPath2 = newbasepath + oRs(1).value;
        __Log("Kopie " + vAttachPath + " Naar " + vAttachPath2);
        try
        {
            if (fso.FolderExists(vAttachPath))
            {
                __Log("xAbout to copy " + vAttachPath + "\\*.* to " + vAttachPath2 + "\\");
                CreateFullPath(vAttachPath2);
                fso.CopyFile(vAttachPath+"\\*.*", vAttachPath2, false); // Don't overwrite
            }
        }
        catch(ee)
        {
            __SafeDoLog("Copying files from " + vAttachPath + "<br>to " + vAttachPath2 + " failed:<br>" + ee.description, "#FFFF00");
        }
        oRs.MoveNext();
    }
    fso = null;
}

// ================================================================================================
// Save de kenmerken die we met listKenmerk hebben aangemaakt
// Verondersteld POST formulier (we doen een Request.Form)
// kenmerkTable    : "mld_kenmerkopdr"
// kenmerkParentKey: "mld_opdr_key"
// kenmerkWaarde   : "mld_kenmerkopdr_waarde"
// kenmerkKey      : "mld_kenmerk_key"
// currentKenmerkenSQL : Voor maken array met kenmerk_keys die voor dit object al in de database waren
//                       als tracking dan moet ook tweede parameter de oude waarde opleveren
// RequestQ        : true voor QueryString, false voor Form
// moduleName      : optional "ALG_ONRGOED_NIVEAU"
// moduleVal       : optional "R"
// isNew           : optional, true als het object nieuw is
// flexPath        :           die gebruiken we dan om tijdelijke folder te hernoemen bijv. "ALG/R"
// tracking        : optional, dan tracken we als !isNew
//                           - allKenmerkenSQL is dan ook verplicht met kenmerk_omschrijving en
//                             srtkenmerk_key (zie melding_save)
//                           - module is dan ook verplicht
// In listKenmerk hebben we al gezorgd dat er alleen waarden gesubmit worden van velden die we
// ook echt mochten wijzigen. kkn en kvn zullen er dan ook altijd beide zijn
// ================================================================================================
// Let op: de voorkeur is het gebruik van saveFlexKenmerken(parentKey, params)
//         voor backward compatibilitiet mag je het nog uitschrijven.
// bez_edit_bezoekers_save, cnt_contract_save en mld_opdr_save gebruiken al de nieuwe methode
function saveFlexKenmerken(parentKey, kenmerkTable /* eigenlijk: Params */,
                           // deprecated: */
                           kenmerkParentKey,
                           kenmerkWaarde, kenmerkKey, currentKenmerkenSQL, RequestQ,
                           moduleName, moduleVal,
                           isNew, flexPath,
                           multiMode) // TODO: support multiMode!
{
    if (typeof kenmerkTable == "object")
    {
        var kenmerkParams = kenmerkTable;
    }
    else // oldstyle
    {
        var kenmerkParams = { kenmerkTable: kenmerkTable,
                              kenmerkParentKey : kenmerkParentKey,
                              kenmerkWaarde: kenmerkWaarde,
                              kenmerkKey: kenmerkKey,
                              currentKenmerkenSQL: currentKenmerkenSQL,
                              requestQF: RequestQ?Request.QueryString:Request.Form,
                              moduleName: moduleName, moduleVal: moduleVal,
                              isNew: isNew, flexPath: flexPath,
                              multiMode: multiMode
                            }
    }

    var flexdata = kenmerkParams.flexdata;
    if (!flexdata)
    {
        // Maak alsnog het flexdata object: backwards compatible!
        var params = {};
        if (kenmerkParams.extraserie) params.extraserie = kenmerkParams.extraserie;
        if (kenmerkParams.nameprefix) params.nameprefix = kenmerkParams.nameprefix;
        flexdata = flexkenmerken2jsondata(params);
    }
    return _saveFlexKenmerken(parentKey, kenmerkParams, flexdata);
}
function _saveFlexKenmerken(parentKey, params, flexdata) // TODO: support multiMode!
{
    var kenmerk_keys = new Array(); // kenmerk table is used to check whether a kenmerk already exists or not.

    if (params.currentKenmerkenSQL) // JGL: Is volgens mij altijd wel aanwezig
    {
        // FSN#18613/ PNBR#22893 Foute kenmerken opruimen
        // Kunnen met name onstaan als iemand bijvoorbeeld de stdmelding wijzigt van een melding,
        // srtactiviteit van een reservering etc.
        // params.currentKenmerkenSQL bevat alle geldige kenmerkwaarden (ongeacht autorisatie)
        if (!params.noFlexCleanup)
        {
            var sqldelete = "DELETE FROM " + params.kenmerkTable
                          + " WHERE " + params.kenmerkParentKey + " = " + parentKey
                          + "   AND " + params.kenmerkKey + " NOT IN (SELECT " + params.kenmerkKey + " FROM (" + params.currentKenmerkenSQL + "))";
            if (params.moduleName) // voorzichtig
                sqldelete += " AND " + params.moduleName + " = " + safe.quoted_sql(params.moduleVal);

            Oracle.Execute(sqldelete);
        }
        // Nu oude kenmerken verzamelen in kenmerk_keys
        var oRs = Oracle.Execute(params.currentKenmerkenSQL);
        while (!oRs.eof)
        {
          kenmerk_keys[oRs(params.kenmerkKey).value] = oRs(params.kenmerkWaarde).value; // kenmerkwaarde moet oldval bevatten
          oRs.MoveNext();
        }
        oRs.Close();
    }

    if (params.isNew && !params.noAttachments)
    {
        var ktmpfolder = getFParam("ktmpfolder" + (params.tmpfolderNum? params.tmpfolderNum : 0), "");
        if (ktmpfolder)
        {
            var tmp_folder =  "__NEW__" + user_key + "_" + safe.filename(ktmpfolder);
            vAttachPath = S("flexFilesPath") + "/" + params.flexPath + tmp_folder;
            var flexPathArray = params.flexPath.split("/"); // MLD/M => MLD (0) en M (1)
            vAttachPath2 = S("flexFilesPath") + "/" + flexPathArray[0] + "/" + subfolderKey(bepaalNiveau(params.moduleName, flexPathArray[1]), parentKey);
            // Niet van toepassing: we doen altijd een hele folder hieronder.
            // MoveFlexfiles(vAttachPath, vAttachPath2)
		        var fso = Server.CreateObject("Scripting.FileSystemObject")
            if (fso.FolderExists(vAttachPath))
            {
                __Log("Hernoem " + vAttachPath + " naar " + vAttachPath2);
                CreateFullPath(vAttachPath2); // het 1000-tal was er misschien nog niet.
                try
                {
                    fso.MoveFolder(vAttachPath + "/*.*", vAttachPath2);
                    fso.DeleteFolder(vAttachPath);
                }
                catch (ee)
                {
                    __DoLog("MoveFolder " + vAttachPath + " naar " + vAttachPath2 + " nog mislukt: " + ee.description);
                }
            }
            fso = null;
        }
    }

    var trackarray = []; // eventuele tracking

    var hasExpression = false;
    for (kenmerkkey in flexdata)
    {
        var flex = flexdata[kenmerkkey];
        var vold = kenmerk_keys[flex.id]? kenmerk_keys[flex.id] : "";
        hasExpression = hasExpression || flex.expr == 1;
        var vnew = flex.value || "";

        // tracking?
        if (!params.isNew && params.tracking)
        {
            if (vold != vnew)
            {
                var oRsk = Oracle.Execute(params.allKenmerkenSQL + flex.id);
                var label = oRsk("kenmerk_omschrijving").Value;
                var kdomein_key = oRsk("fac_kenmerkdomein_key").Value;
                oRsk.Close();
                if (flex.type == "S" || flex.type == "R")
                {
                    if (vold != "")
                    {
                        var oRsd = Oracle.Execute("SELECT fac.getdomeinwaarde(" + kdomein_key + ", " + safe.quoted_sql(vold) + ") domeinwaarde FROM DUAL");
                        vold = oRsd("domeinwaarde").Value;
                        oRsd.Close();
                    }

                    if (vnew != "")
                    {
                        var oRsd = Oracle.Execute("SELECT fac.getdomeinwaarde(" + kdomein_key + ", " + safe.quoted_sql(vnew) + ") domeinwaarde FROM DUAL");
                        vnew = oRsd("domeinwaarde").Value;
                        oRsd.Close();
                    }
                }
                trackarray.push(label + ": " + buildTrackText("varchar", vold, vnew, {nodiff: (flex.type != "C"? true : null)}));
            }
        }

        // Bepaal of het hier gaat om een flexkemerk verbruiksmeter. Dan moet historie behouden blijven
        var verbruiksmeter = false;
        if (params.kenmerkKey == "ins_kenmerk_key")
        {   // Het gaat om een INS flexkenemerk.
            // Controleren of het hier om een verbruiksmeter gaat.
            var sql2 = "SELECT ins_kenmerk_meetwaarde"
                     + "  FROM ins_kenmerk"
                     + " WHERE ins_kenmerk_key = " + flex.id;
            oRs = Oracle.Execute(sql2);
            verbruiksmeter = oRs("ins_kenmerk_meetwaarde").value == 1;
            oRs.Close();
        }

        // A real kenmerk value exists
        if (flex.type == "X" && S("flex_X_is_URL") == 0)
            flex.value = parseDocumentPath(flex.value);

        if (( flex.value == "" || flex.value == "undefined"))
        {
            if (kenmerk_keys[flex.id] && !params.multiMode)
            {
                if (verbruiksmeter)
                {   // Zet de sysdate zodat de historie behouden blijft
                    sql = "UPDATE ins_kenmerkdeel"
                        + "   SET ins_kenmerkdeel_verwijder = " + "SYSDATE"
                        + " WHERE ins_deel_key = " + parentKey
                        + "   AND ins_kenmerk_key = " + flex.id
                        + "   AND ins_kenmerkdeel_verwijder IS NULL";
                    Oracle.Execute(sql);
                }
                else
                {   // Delete;
                    // Zelf zetten we niet de verwijderdatum maar als een ander proces dat
                    // heeft gedaan (BONS#21991) dan blijven wij wel van die records af.
                    sql = "DELETE FROM " + params.kenmerkTable
                        + " WHERE " + params.kenmerkParentKey + " = " + parentKey
                        + "   AND " + params.kenmerkKey + " = " + flex.id
                        + "   AND " + params.kenmerkTable + "_VERWIJDER IS NULL";
                    Oracle.Execute(sql);
                }
            }
            continue;
        }

        if (flex.id in kenmerk_keys) // Kenmerk komt in de kenmerkTable voor. Desnoods met waarde null (wat eigenlijk niet zou mogen).
        {   // update
            // Alleen een update als de waarde veranderd is
            if (vold != vnew)
            {   // Alleen een update/insert als de waarde veranderd of toegevoegd is
                if (verbruiksmeter)
                {   // Bij de verbruiksmeters moet de historie behouden blijven
                    // Bij verbruiksmeter gaat het over ins kenmerken
                    sql = "UPDATE ins_kenmerkdeel"
                        + " SET ins_kenmerkdeel_verwijder = " + "SYSDATE"
                        + " WHERE ins_deel_key = " + parentKey
                        + " AND ins_kenmerk_key = " + flex.id
                        + " AND ins_kenmerkdeel_verwijder IS NULL";
                    Oracle.Execute(sql);

                    sql = "INSERT INTO ins_kenmerkdeel "
                        + " (ins_kenmerkdeel_waarde, ins_deel_key, ins_kenmerk_key)"
                        + " VALUES "
                        + " (" + safe.quoted_sql(flex.value) + ", " + parentKey + ", " + flex.id + ")";
                    Oracle.Execute(sql);
                }
                else
                {   // Gewone flexkenmerken
                    // Zelf zetten we niet de verwijderdatum maar als een ander proces dat
                    // heeft gedaan (BONS#21991) dan blijven wij wel van die records af.
                    sql = "UPDATE " + params.kenmerkTable + " SET " + params.kenmerkWaarde + " = " + safe.quoted_sql(flex.value)
                        + " WHERE " + params.kenmerkParentKey + " = " + parentKey
                        + "   AND " + params.kenmerkKey + " = " + flex.id
                        + "   AND " + params.kenmerkTable + "_VERWIJDER IS NULL";
                    Oracle.Execute(sql);
                }
            }
        }
        else
        {   // Insert
            if (( flex.value != "" && flex.value != "undefined"))
            {
                sql = "INSERT INTO " + params.kenmerkTable
                    + "(" + params.kenmerkParentKey
                    + "," + params.kenmerkKey
                    + "," + params.kenmerkWaarde
                    + (params.moduleName? "," + params.moduleName : "")
                    + ")"
                    + " VALUES "
                    + "(" + parentKey
                    + "," + flex.id
                    + "," + safe.quoted_sql(flex.value)
                    + (params.moduleName? "," + safe.quoted_sql(params.moduleVal): "")
                    + ")";
                Oracle.Execute(sql);
            }
        }
    }

    if (hasExpression)
    {
      if (params.module == "MLD")
      {
        if (params.flexPath == "MLD/O")
          module = "OPD"
        else
          module = "MLD"
      }
      else
        module = params.module;

      // Tabel fac_kenmwaarden vullen, expressies berekenen en tabel fac_kenmwaarden weer leeg maken.
      // Vorige waarden verwijderen.
      removekenmerkwaarden(module, parentKey);
      savekenmerkwaarden(module, parentKey, params.moduleVal, flexdata);
      evaluateflexexpressions(module, parentKey, true);
      //removekenmerkwaarden(module, parentKey);
    }

    return trackarray;
}

function savekenmerkwaarden(module, refkey, kenmerkniveau, flexdata)
{
  for (kenmerkkey in flexdata)
  {
    var sql = "INSERT INTO fac_kenmwaarden"
            + "            (fac_kenmwaarden_module"
            + "           , fac_kenmwaarden_refkey"
            + (kenmerkniveau
              ? "         , fac_kenmwaarden_niveau"
              : "")
            + "           , fac_kenmwaarden_kenmerk_key"
            + "           , fac_kenmwaarden_waarde"
            + "           , fac_kenmwaarden_kenmerktype"
            + "           , fac_kenmwaarden_isexpr"
            + "           , prs_perslid_key"
            + "            )"
            + "     VALUES (" + safe.quoted_sql(module)
            + "           , " + refkey
            + (kenmerkniveau
              ? "         , " + safe.quoted_sql(kenmerkniveau)
              : "")
            + "           , " + flexdata[kenmerkkey].id
            + "           , " + safe.quoted_sql(flexdata[kenmerkkey].value)
            + "           , " + safe.quoted_sql(flexdata[kenmerkkey].type)
            + "           , " + flexdata[kenmerkkey].expr
            + "           , " + user_key
            + "            )";
    Oracle.Execute(sql);
  }
}

function evaluateflexexpressions(module, refkey, savetodb)
{ // Berekening van de expressie kenmerken.
  var sql = "BEGIN flx.evaluateflexexpressions(" + safe.quoted_sql(module) + ", " + refkey + ", " + user_key + ", " + (savetodb? 1 : 0) + "); END;";
  var err = Oracle.Execute(sql, true);
  if (err.friendlyMsg)
  {
    var message = "kenmerk_common.inc (evaluateflexexpressions): Error writing flex expression\n" + err.friendlyMsg;
    __DoLog(message);
    abort_with_warning(err.friendlyMsg);
  }
}

function removekenmerkwaarden(module, refkey)
{
  var sql = "DELETE FROM fac_kenmwaarden"
          + " WHERE fac_kenmwaarden_module = " + safe.quoted_sql(module)
          + "   AND fac_kenmwaarden_refkey = " + refkey
          + "   AND prs_perslid_key = " + user_key;
  Oracle.Execute(sql);
}

function flexkenmerken2jsondata(params)
{
    // Lees de Form parameters van flexkenmerken en zet ze in een json-object.
    var jsondata = [];
    var namePrefix  = params.nameprefix || "k";
    var counterName = (params.extraserie? namePrefix + "_all" : "k_all");

    if (Request.Form(counterName).count > 0)
        var kall = Request.Form(counterName)(1); // bij bezoekers komt counterName (k_all) meerdere keren voor maar wel altijd identiek
    else
    {   // JGL: komt normaal nooit voor, alleen als edit-scherm flexkenmerken niet goed heeft kunnen laden, bijv. AKZA#21569
        //      Normaal laten we zoiets gewoon gebeuren maar
        //          Error: File /facilitor5iwork/appl/shared/kenmerk_common.inc Line 1246 Index out of range. An array index is out of range.
        //      in de eventvwr(!) wil ik echt niet hebben
        var kall = 0;
    }

    for (var kenmerk=1; kenmerk<=kall; kenmerk++)
    {
        var flex = namePrefix + kenmerk;
        var kkn = Request.Form(flex + "key").Count;
        var ktn = Request.Form(flex + "t").Count;
        if( kkn && ktn )
        {
            var kk = getFParamInt(flex + "key");     // kenmerk key
            var kt = getFParam(flex + "t", "");      // Type letter
            var kv = getFParam(flex + "val", "");    // kenmerk waarde
            var ke = Request.Form(flex + "e").Count; // Flexkenmerk is een expressie.

            // ATTENTIE: Als het een numeriek/decimaal veld is, kan het zijn dat er een komma in voorkomt die eigenlijk een punt moet zijn.
            //           Als het een numeriek/decimaal veld is, kan het ook een checkbox zijn
            if (kt == "N")
            {
                kv = kv.replace(",", ".");     // We slaan altijd op met punten
                if (kv.substr(0, 2) == "0.")   // leading zero er af. Doet de database trigger ook en nu
                  if (kv.length == 2 || kv.substr(3) == " ")
                    kv = "0";
                  else
                    kv = kv.substr(1);         // krijgen we geen ongewenste tracking.
                // Kijken of het een checkbox is
                var isCheckbox = getFParamInt("has_" + flex + "val", 0) == 1;
                if (isCheckbox)
                {
                    var isChecked = Request.Form(flex + "val").Count;
                    kv = (isChecked ? "1" : ""); // Een niet aangevinkte checkbox hoeft niet opgeslagen te worden als de waarde "0".
                }
            }

            // Suggest kenmerken moeten ook gedelete worden als ze leeg zijn. Dus voor delete actie leeg maken.
            // Ook nodig voor correctere tracking
            if ((kt == "S" || kt == "R") && kv == "-1") kv = ""; // anders zouden we '-1' opslaan

            jsondata[kk] = { id: kk,
                             //name: "",
                             type: kt,
                             value: kv,
                             expr: (ke? 1 : 0),
                             kfieldid: flex
                           };
        }
    }
    return jsondata;
}
%>
