<% /*
     $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)

*/

// 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", "suggest"],
                          js: ["multi.js"] });

var requiredGroupUsed = new Array();    // Array zorgt voor eenmalige initiele aanroep van checkRequiredGroup(groep) per groep
var addPreview        = false;          // moet deze global?
var  __insideblock    = 0;
var clabelCounter     = 0;              // Het aantal collapsable labels (niet zeker of nog noodzakelijk)
var show_label_expr   = {};

// Public function: read the kenmerk_key in case of <kenmerk> with refcode or value
function getKenmerkKeyByRef(kenmerkdomein_key, kenmerk_ref, kenmerk_waarde)
{
    var kenmerk_key = parseInt(kenmerk_waarde, 10);
    var fac_omschr_code;
    var udf_omschr_code;
    var ksql = "SELECT fac_kenmerkdomein_objectnaam, fac_kenmerkdomein_kolomnaam, fac_kenmerkdomein_kolomtxt, fac_kenmerkdomein_verval, fac_usrtab_key"
             + "  FROM fac_kenmerkdomein"
             + " WHERE fac_kenmerkdomein_key = " + kenmerkdomein_key
             + "   AND fac_kenmerkdomein_verwijder IS NULL";
    var oRs_kd = Oracle.Execute(ksql);
    if (!oRs_kd.eof)
    {
        switch (kenmerk_ref)
        {
          case "value":
            udf_omschr_code = " WHERE UPPER(" + oRs_kd("fac_kenmerkdomein_kolomtxt").Value + ") = " + safe.quoted_sql_upper(kenmerk_waarde);
            fac_omschr_code = "   AND UPPER(fac_usrdata_omschr) = " + safe.quoted_sql_upper(kenmerk_waarde);
            break;
          case "code":
            udf_omschr_code = " WHERE UPPER(" + oRs_kd("fac_kenmerkdomein_objectnaam").Value + "_CODE) = " + safe.quoted_sql_upper(kenmerk_waarde);
            fac_omschr_code = "   AND UPPER(fac_usrdata_code) = " + safe.quoted_sql_upper(kenmerk_waarde);
            break;
          default:
            oRs_kd.Close();
            return kenmerk_key;
        }
        var kenmerkdomein_verval = oRs_kd("fac_kenmerkdomein_verval").Value;
        if (oRs_kd("fac_kenmerkdomein_objectnaam").Value == "FAC_USRDATA" && oRs_kd("fac_usrtab_key").Value)
        {
          ksql = "SELECT fac_usrdata_key"
               + " FROM fac_usrdata"
               + " WHERE fac_usrtab_key = " + oRs_kd("fac_usrtab_key").Value
               + fac_omschr_code
               + " AND (fac_usrdata_vervaldatum IS NULL OR fac_usrdata_vervaldatum > SYSDATE)"
               + " AND fac_usrdata_verwijder IS NULL";
        }
        else
        {
          ksql = "SELECT " + oRs_kd("fac_kenmerkdomein_kolomnaam").Value + " fac_usrdata_key"
               + "  FROM " + oRs_kd("fac_kenmerkdomein_objectnaam").Value
               + udf_omschr_code
               + (kenmerkdomein_verval != null ? " AND ( " + kenmerkdomein_verval + " IS NULL OR " + kenmerkdomein_verval + " > SYSDATE)" : "");
        }
    }
    var oRs_ud = Oracle.Execute(ksql);
    if (!oRs_ud.eof)
        kenmerk_key = String(oRs_ud("fac_usrdata_key").Value);

    oRs_ud.Close();
    oRs_kd.Close();
    return kenmerk_key;
}

//
// Internal functions:
//
function kenmerk_write(s)
{
    Response.Write(s);
}

// links aanvullen met 0
function pad(val, len)
{
    if (typeof val != "string")
        return val;
    while (val.length < len)
        val = "0" + val;
    return val;
}
// levert .. eeh..
function get_kenmerkpath_params(path)
{
    var path_params = {};
    var path_arr = path.split("/");
    if (path_arr.length > 2)
    {
        path_params.module = path_arr[0];
        path_params.niveau = path_arr[1].replace(/\d+/g, "").replace(/_/g, "");
        path_params.refkey = path_arr[2].replace(/\D/g,"");
        if (path_arr[3])
            path_params.kenmerkkey = path_arr[3];
    }
    return path_params;
}
// Tel/ zoek de bijlagen in een folder
// Resultaat: array met bijlagenamen
function BijlagenList(pModule, pNiveau, pKey, pKenmerk_key, otherKenmerk_path, pSortFilename)
{
    if (pKey == -1 && otherKenmerk_path)
    {
        var kp_params = get_kenmerkpath_params(otherKenmerk_path);
        pKenmerk_key = kp_params.kenmerkkey;
        pKey = kp_params.refkey;
    }

    var from_table = true;
    var lijst = ( from_table
                ? BijlagenListFromTable(pModule, pNiveau, pKey, pKenmerk_key, pSortFilename)
                : BijlagenListFromPath(pModule, pNiveau, pKey, pKenmerk_key, otherKenmerk_path)
                );
    return lijst;
}

function BijlagenListFromPath(pModule, pNiveau, pKey, pKenmerk_key, otherKenmerk_path)
{
    function listFromPath(pAttachPath)
    {
        var fso = Server.CreateObject("Scripting.FileSystemObject")
        var vLijst = [];
        if (pAttachPath)
            __Log("Tellen in folder: " + pAttachPath);
        if (pAttachPath && fso.FolderExists(pAttachPath))
        {
            var fso, f, f1, fc, s;
            fso = new ActiveXObject("Scripting.FileSystemObject");
            f = fso.GetFolder(pAttachPath);
            fc = new Enumerator(f.files);

            for (; !fc.atEnd(); fc.moveNext())
            {
                vLijst.push({ "name": fc.item().Name, "date": new Date(fc.item().DateLastModified), "size": fc.item().Size });
            }
        }
        return vLijst;
    }

    function arrayUnique(a, b)
    {   // Voeg 2 arrays samen en ontdubbel op basis van het veld name.
        var c = a.concat(b);
        for (var i=0; i<c.length; ++i)
        {
            for(var j=i+1; j<c.length; ++j)
            {
                if (c[i].name == c[j].name)
                    c.splice(j--, 1);
            }
        }
        return c;
    }


    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_1 = listFromPath(vAttachPath);
    if (vAttachPath)
    {
        __Log("{0} bestanden gevonden in Path {1}.".format(lijst_1.length, vAttachPath));
    }

    // Zoek ook in de root (via fac_bijlagen)
    var r_bijlagen = get_bijlagen(pModule, pKey, pKenmerk_key);
    var lijst_2 = [];
    if (r_bijlagen.length)
    {
        var vAttachPath = flexProps(pModule, r_bijlagen[0].refkey, String(r_bijlagen[0].kmkey), r_bijlagen[0].niveau).AttachPath + "/";
        lijst_2 = listFromPath(vAttachPath);
        __Log("{0} bestanden gevonden in Path {1}.".format(lijst_2.length, vAttachPath));
    }

    // Verwijder dubbele bestanden.
    var lijst = arrayUnique(lijst_1, lijst_2);
    __Log("{0} bestanden gevonden in Path.".format(lijst.length));

    return lijst;
}

function BijlagenListFromTable(pModule, pNiveau, pKey, pKenmerk_key, pSortFilename)
{
    function compareValue(order_by_filename)
    {
        return function innerSort(a,b)
                {
                    if (order_by_filename)
                    {
                        var c = 0;
                        if (a.name.toUpperCase() > b.name.toUpperCase())
                            c = 1;
                        else if (a.name.toUpperCase() < b.name.toUpperCase())
                            c = -1;
                        return c;
                    }
                    else
                    {
                        return new Date(a.date) - new Date(b.date);
                    }
                }
    }

    var lijst = [];
    if (pKey > 0)
    {
        lijst = get_bijlagen(pModule, pKey, pKenmerk_key, null, pNiveau);
        lijst.sort(compareValue(pSortFilename));
        __Log("{0} bestanden gevonden in Table.".format(lijst.length));
    }

    return lijst;
}

// debug functie die vergelijkt wat op schijf staat versus in fac_bijlagen
function match_fac_bijlagen(pModule, pKey, pKenmerk_key, pNiveau, otherKenmerk_path)
{
//    if (Application("JGL") != "1" || (!pKey > 0))
//        return "";

    var JGLerrors = [];

    var filesystem_lijst = BijlagenListFromPath(pModule, pNiveau, pKey, pKenmerk_key, otherKenmerk_path);
    if (pKey == -1 && otherKenmerk_path)
    {   // Bij kopieren van melding naar opdracht moeten de bestanden bij de melding worden gezocht.
        var kp_params = get_kenmerkpath_params(otherKenmerk_path);
        pKenmerk_key = kp_params.kenmerkkey;
        pKey = kp_params.refkey;
    }
    var lijst2 = BijlagenListFromTable(pModule, pNiveau, pKey, pKenmerk_key);  // Deze kijkt welke bijlagen in de tabel fac_bijlagen staan
    for (var i = 0; i < filesystem_lijst.length; i++)
    {
        var name = filesystem_lijst[i].name;
        for (var j = 0; j < lijst2.length; j++)
        {
            if (lijst2[j].name == filesystem_lijst[i].name)
            {
                lijst2[j].name = null;
                break;
            }
        }
        if (j == lijst2.length)
            JGLerrors.push(L("lcl_fac_bijlagen_notregistered").format(filesystem_lijst[i].name))
    }
    for (var j = 0; j < lijst2.length; j++)
    {
        if (lijst2[j].name)
        {
            JGLerrors.push(L("lcl_fac_bijlagen_notfound").format(lijst2[j].name))
        }
    }
    if (JGLerrors.length)
    {
        __Log(">>>MATCH_FAC_BIJLAGEN");
        var msg = "Errors (LOG only) m:{0} N:{1} K:{2} KK:{3}".format(pModule, pNiveau, pKey, pKenmerk_key);
        __Log(msg);
        __Log(JGLerrors.join("\n"));
        __Log("<<<");
        return JGLerrors.join("<br>");
    }
    else
        return "";
}

function BijlagenButton(tfield, tprops, tparams)
{   // Module is 'R' voor ruimte, 'M' voor melding enz
    // key is de object key
    // Parameter tfield.otherPath 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] */

    // verschillende situaties voor kopieren van bijlagen van melding naar opdracht, opdracht naar opdracht en melding/opdracht naar meerdere opdrachten
    // #63784 en #63771
    var lijst = [];
    var kenmerk_key = tfield.kkey;

    if (tparams.module == "MLD")
    {
        if (tprops.copy)
        {   // De uitzonderingen zitten in meldingen en opdrachten.
            // Voor andere modules kan er 1 op 1 gekopieerd worden.
            if (tprops.niveau == "M")
            {   // Melding naar melding
                var sql = "SELECT mld_stdmelding_key"
                        + "  FROM mld_melding"
                        + " WHERE mld_melding_key = " + tprops.parentKey;
                var oRs = Oracle.Execute(sql);
                var old_stdmelding_key = oRs("mld_stdmelding_key").Value;
                oRs.Close();
                var actual_kenmerk_key = kenmerk_key; /* Deze is mogelijk anders als we van stdm wijzigen */
                if (old_stdmelding_key != tprops.stdm_key) {
                    sql = "SELECT k2.mld_kenmerk_key"
                        + "  FROM mld_kenmerk     k1"
                        + "     , mld_kenmerk     k2"
                        + "     , mld_srtkenmerk  sk1"
                        + "     , mld_srtkenmerk  sk2"
                        + " WHERE k1.mld_srtkenmerk_key = k2.mld_srtkenmerk_key"
                        + "   AND k1.mld_srtkenmerk_key = sk1.mld_srtkenmerk_key"
                        + "   AND k2.mld_srtkenmerk_key = sk2.mld_srtkenmerk_key"
                        + "   AND sk1.mld_srtkenmerk_key = sk2.mld_srtkenmerk_key"
                        + "   AND COALESCE (k1.mld_kenmerk_omschrijving, sk1.mld_srtkenmerk_omschrijving) ="
                        + "       COALESCE (k2.mld_kenmerk_omschrijving, sk2.mld_srtkenmerk_omschrijving)"
                        + "   AND sk1.mld_srtkenmerk_kenmerktype = sk2.mld_srtkenmerk_kenmerktype" // Toch wel op z'n minst
                        + "   AND k1.mld_kenmerk_groep = k2.mld_kenmerk_groep"
                        + "   AND k1.mld_kenmerk_key = " + kenmerk_key
                        + "   AND (   (    k1.mld_stdmelding_key IS NULL"
                        + "            AND k1.mld_kenmerk_niveau = 'A')"
                        + "        OR (    k1.mld_stdmelding_key = " + tprops.stdm_key
                        + "            AND k1.mld_kenmerk_niveau = 'S')"
                        + "        OR (    k1.mld_stdmelding_key = (SELECT s.mld_ins_discipline_key"
                        + "                                           FROM mld_stdmelding s"
                        + "                                          WHERE s.mld_stdmelding_key = " + tprops.stdm_key + ")"
                        + "            AND k1.mld_kenmerk_niveau = 'D')"
                        + "        OR (    k1.mld_stdmelding_key = (SELECT d.ins_srtdiscipline_key"
                        + "                                           FROM mld_stdmelding s, mld_discipline d"
                        + "                                          WHERE s.mld_ins_discipline_key = d.ins_discipline_key"
                        + "                                            AND s.mld_stdmelding_key = " + tprops.stdm_key + ")"
                        + "            AND k1.mld_kenmerk_niveau = 'T'))"
                        + "   AND (   (    k2.mld_stdmelding_key IS NULL"
                        + "            AND k2.mld_kenmerk_niveau = 'A')"
                        + "        OR (    k2.mld_stdmelding_key = " + old_stdmelding_key
                        + "            AND k2.mld_kenmerk_niveau = 'S')"
                        + "        OR (    k2.mld_stdmelding_key = (SELECT s.mld_ins_discipline_key"
                        + "                                           FROM mld_stdmelding s"
                        + "                                          WHERE s.mld_stdmelding_key = " + old_stdmelding_key + ")"
                        + "            AND k2.mld_kenmerk_niveau = 'D')"
                        + "        OR (    k2.mld_stdmelding_key = (SELECT d.ins_srtdiscipline_key"
                        + "                                           FROM mld_stdmelding s, mld_discipline d"
                        + "                                          WHERE s.mld_ins_discipline_key = d.ins_discipline_key"
                        + "                                            AND s.mld_stdmelding_key = " + old_stdmelding_key + ")"
                        + "            AND k2.mld_kenmerk_niveau = 'T'))";
                    var oRs = Oracle.Execute(sql);
                    if (oRs.EoF)
                        actual_kenmerk_key = -1; // Dan weet ik het ook niet
                    else
                    {
                        actual_kenmerk_key = oRs("mld_kenmerk_key").Value;
                        oRs.MoveNext();
                        if (!oRs.EoF) // Dan hebben we schijnbaar geen unieke gevonden -> foute configuratie
                            actual_kenmerk_key = -1;
                    }
                    oRs.Close();
                }
                if (actual_kenmerk_key > 0) {
                    lijst = BijlagenList(tparams.module, tprops.niveau, tparams.pkey, actual_kenmerk_key, tfield.otherPath, tprops.sortFilename);
                }
            }
            else if (tfield.otherPath) // tprops.niveau == "O" [mld_kenmerk_niveau IN ('O', 'P')]
            {   // Opdracht naar opdracht
                var sql = "SELECT mld_typeopdr_key"
                        + "  FROM mld_opdr"
                        + " WHERE mld_opdr_key = " + tprops.parentKey;
                var oRs = Oracle.Execute(sql);
                var old_typeopdr_key = oRs("mld_typeopdr_key").Value;
                oRs.Close();
                var actual_kenmerk_key = kenmerk_key; /* Deze is mogelijk anders als we van opdr-type wijzigen */
                if (old_typeopdr_key != tprops.opdr_type)
                {
                    sql = "SELECT k2.mld_kenmerk_key"
                        + "  FROM mld_kenmerk     k1,"
                        + "       mld_kenmerk     k2,"
                        + "       mld_srtkenmerk  sk1,"
                        + "       mld_srtkenmerk  sk2"
                        + " WHERE k1.mld_srtkenmerk_key = k2.mld_srtkenmerk_key"
                        + "   AND k1.mld_srtkenmerk_key = sk1.mld_srtkenmerk_key"
                        + "   AND k2.mld_srtkenmerk_key = sk2.mld_srtkenmerk_key"
                        + "   AND COALESCE (k1.mld_kenmerk_omschrijving, sk1.mld_srtkenmerk_omschrijving) ="
                        + "       COALESCE (k2.mld_kenmerk_omschrijving, sk2.mld_srtkenmerk_omschrijving)"
                        + "   AND sk1.mld_srtkenmerk_kenmerktype = sk2.mld_srtkenmerk_kenmerktype" // Toch wel op z'n minst
                        + "   AND k1.mld_kenmerk_key = " + kenmerk_key
                        + "   AND (   k1.mld_typeopdr_key = " + tprops.opdr_type + " AND k1.mld_kenmerk_niveau = 'O'"
                        + "        OR k1.mld_typeopdr_key IS NULL AND k1.mld_kenmerk_niveau = 'P')"
                        + "   AND (   k2.mld_typeopdr_key = " + old_typeopdr_key + " AND k2.mld_kenmerk_niveau = 'O'"
                        + "        OR k2.mld_typeopdr_key IS NULL AND k2.mld_kenmerk_niveau = 'P')"
                        + "   AND k2.mld_kenmerk_verwijder IS NULL";
                    var oRs = Oracle.Execute(sql);
                    if (oRs.EoF)
                        actual_kenmerk_key = -1; // Dan weet ik het ook niet
                    else
                    {
                        actual_kenmerk_key = oRs("mld_kenmerk_key").Value;
                        oRs.MoveNext();
                        if (!oRs.EoF) // Dan hebben we schijnbaar geen unieke gevonden -> foute configuratie
                            actual_kenmerk_key = -1;
                    }
                    oRs.Close();
                }
                if (actual_kenmerk_key > 0)
                    lijst = BijlagenList(tparams.module, tprops.niveau, tparams.pkey, actual_kenmerk_key, tfield.otherPath, tprops.sortFilename);
            }
        }
        else if (tprops.srtkm_list && tprops.srtkm_list.length > 0)
        {
            for (var i=0; i<tprops.srtkm_list.length; i++)
            {
                var v_srtkm = tprops.srtkm_list[i];
                var mld_parent_lijst = BijlagenList(tparams.module, tprops.niveau, v_srtkm.mld_key, [v_srtkm.km_key], tfield.otherPath);
                lijst = lijst.concat(mld_parent_lijst);
            }
        }
        else
        {   // Melding naar opdracht
            // Alleen de mappen met bijlagen toevoegen die we nog niet eerder hebben toegevoegd.
            if (tfield.kenmerktype != "M" || !tfield.otherPath || !inArray(tfield.otherPath, tparams.attach_km_list))
            {   // In workflow ook alle bijlage kenmerken ophalen van voorgaande meldingen,
                // voor zover deze gewenst zijn.
                var km_arr = [kenmerk_key];
                if (tprops.parent_list && tprops.parent_list.length > 0)
                {
                    for (var i=0; i<tprops.parent_list.length; i++)
                    {
                        var v_parent = tprops.parent_list[i];
                        if (v_parent.km_key && v_parent.km_key.length > 0)
                        {
                            for (var j=0; j<v_parent.km_key.length; j++)
                            {
                                km_arr.push(v_parent.km_key[j]);
                            }
                        }
                    }
                }
                lijst = BijlagenList(tparams.module, tprops.niveau, tparams.pkey, km_arr, tfield.otherPath);
            }
        }
    }
    else // Voor alle andere modules
    {   // Bij kopieren van bijlagen bij opdracht niet naar specifieke kenmerk zoeken.
        kenmerk_key = ((tfield.kkenmerktype == "M" && tprops.copy) ? -1 : tfield.kkey);
        lijst = BijlagenList(tparams.module, tprops.niveau, tparams.pkey, kenmerk_key, tfield.otherPath, tprops.sortFilename);
    }

    // var mismatchtekst = match_fac_bijlagen(tparams.module, tparams.pkey, kenmerk_key, tprops.niveau, tfield.otherPath);
    var mismatchtekst = "";
    var anyPreview = false;
    var fileChecked = ( tparams.pkey == -1 ? S("mld_copy_order_attachment_from_call") : true);
    var requiredfield = (tfield.required ? tparams.required_class : "");

    if (tfield.readonlyfield || (tfield.otherPath && lijst.length > 0))
    {   // Indien readonly en lijst.length = 0 dan hoeft het kenmerk (net als bij andere kenmerken) helemaal niet getoond te worden.
        var filehtmlarr = [];
        var max_attach = S("fac_max_initial_attachments");
        var regex = new RegExp(S("flexPreviewExt"), 'i');
        if (tfield.otherPath) {
            var otherKenmerk_arr = tfield.otherPath.split("/");
            var otherKenmerk_key = otherKenmerk_arr.length ?  otherKenmerk_arr[otherKenmerk_arr.length - 1] : -1;
        }
        var attach_counter = 0;
        for (var file in lijst)
        {
            ++attach_counter;
            if (tfield.kkenmerktype == "M") {
                var kmCode = function(text, no_file) { return tprops.nameprefix + tfield.idCounter + text  + (no_file? "" : file); }
                var km_val = kmCode('val', true);
            } else {
                var kmCode = function(text) { return tprops.nameprefix + tfield.idCounter + text; }
                var km_val = kmCode('val');
            }
            var html = (tfield.otherPath
                     ? '<input type="checkbox" class="fldCflexN" '
                       + (fileChecked ? " checked" : "")
                       + (tfield.readonlyfield ? " disabled" : "" )
                       +' controlgroup="'+kenmerk_key+'" id="' + kmCode('val') + '" name="' + kmCode('val') + '">'
                       + '<label for="' + kmCode('val') + '">' + safe.html(lijst[file].name)+'</label>'
                       + (tfield.kkenmerktype == "M"
                         ? '<input type="hidden" id="has_' + kmCode('val') + '"  name="has_' + kmCode('val') + '" value="1">'
                         + '<input type="hidden" id="' + kmCode('file') + '" name="' + kmCode('file') + '" value="' + safe.html(lijst[file].name) + '">'
                         : '')
                    : '');

            //var html = "<a href='" + safe.htmlattr(href) + "' target='_new'>" + safe.html(lijst[file]) + "</a>";
            if (tfield.readonlyfield && !tfield.otherPath)
            {
              if (tfield.kdimensie == "xxx")
              {
                  var href = protectQS.create(OpenFlexFile(tparams.module, lijst[file].key, { mime_type: "image/png", contentdp: "inline" }));
                  html += "<img alt='" + safe.htmlattr(L("lcl_flex_preview")) + "' class='preview kenmerk' src='" + safe.htmlattr(href) + "'"
                        + " onclick='FcltMgr.windowopen(\"" + safe.jsstring(href) + "\")'>"
                        + "<span class='flexsize'>{0}</span>".format(toDateTimeString(lijst[file].date));
              }
              else
              {
                  var streamparams = ( tprops.mobile && (lijst[file].mimetype == "application/pdf" || lijst[file].mimetype.substr(0,5) == "image")
                                     ? { mime_type: lijst[file].mimetype
                                       , contentdp: "inline"
                                       }
                                     : {}
                                     );
                  var href = protectQS.create(OpenFlexFile(tparams.module, lijst[file].key, streamparams));
                  html += '<span class="details ' + (max_attach > 0 && attach_counter > max_attach? km_val + " hide-more hidden" : "") + '" readonly=1 title="{0} kB"'.format(Math.round(lijst[file].size / 1024))
                       + ' onclick=\'FcltMgr.windowopen("' + safe.jsstring(href) + '", "File")\'>' + safe.html(lijst[file].name) + '</span>'
                       + ' <span class="flexsize ' + (max_attach > 0 && attach_counter > max_attach? km_val + " hide-more hidden" : "") + '">{0}</span>'.format(toDateTimeString(lijst[file].date));
              }
              if (regex.test(lijst[file].name)) {
                anyPreview = true;
              }
            }
            filehtmlarr.push(html);
        }

        function checkCheckbox(event) {
          $("#has_" + $(event.target).attr("id")).val($(event.target).is(":checked") ? "1" : "0");
          if ($(event.target).closest("div").hasClass("required"))
          {
            $("[controlgroup=" + $(event.target).attr("controlgroup") + "]").attr("disabled", false);
            var arr = $("[controlgroup=" + $(event.target).attr("controlgroup") + "]:checked");
            if (arr.length == 1)
            {
              event.preventDefault();
              $(arr[0]).attr("disabled", true);
              $(arr[0]).attr("checked", true);
            }
          }
        }

        var frm = '';
        var res = '';
        if (filehtmlarr.length)
        {
            var res = "<div>" + filehtmlarr.join("<br>")
            if (max_attach > 0)
            {
                res += '<div class="' + km_val + ' show-more-container" style="display: none">'
                     + '  <div class="' + km_val + ' show-more"><span class="details">'+L("lcl_more")+'</span></div>'
                     + '  <div class="' + km_val + ' show-less hidden"><span class="details">'+L("lcl_less")+'</span></div>'
                     + '<script>$(function(){ '
                     + '          $("span.' + km_val + '.hide-more").next("br").toggleClass("hidden invisible");'
                     + '          $("div.' + km_val + '.show-more-container").toggle(' + lijst.length + ' > ' + max_attach + ');'
                     + (lijst.length > max_attach ? 'FcltMgr.resized(window);' : '') /* Want ik heb zojuist getoggled */
                     + '          $("div.' + km_val + '.show-more, div.' + km_val + '.show-less").off().on("click", function() { '
                     + '            $("div.' + km_val + '.show-more, div.' + km_val + '.show-less, span.' + km_val + '.hide-more").toggleClass("hidden");'
                     + '            $("span.' + km_val + '.hide-more").next("br").toggleClass("hidden");'
                     + '            FcltMgr.resized(window);'
                     + '          });'
                     + '        });'
                     + '</script>';
            }
            res += "</div>";
            if (tfield.otherPath)
            {
                res += '<input type="hidden" name="' + tprops.nameprefix + tfield.idCounter + 'key"       id="' + tprops.nameprefix + tfield.idCounter + 'key"       value="' + kenmerk_key + '">'
                    +  '<input type="hidden" name="' + tprops.nameprefix + tfield.idCounter + 'other_key" id="' + tprops.nameprefix + tfield.idCounter + 'other_key" value="' + (otherKenmerk_key || -1) + '">'
                    +  '<input type="hidden" name="' + tprops.nameprefix + tfield.idCounter + 't"         id="' + tprops.nameprefix + tfield.idCounter + 't"         value="' + tfield.kkenmerktype + '">'
                    +  '<input type="hidden" name="' + tprops.nameprefix + tfield.idCounter + 'bval"      id="' + tprops.nameprefix + tfield.idCounter + 'bval"      value="' + safe.html(lijst[file].name)+ '">';
            }
            res += "</div>";
            if (tfield.otherPath)
                res += '<script>$(function(){ $("[controlgroup='+kenmerk_key+']").on("change", '+checkCheckbox+').change();});</script>';
        }
        if (mismatchtekst)
        {
            var msg = "";
            res += "<div style='color:red'>{0}<br>{1}</div>".format(msg, mismatchtekst);
        }

        // Nieuwe bijlagen toevoegen.
        if (!tprops.reado)
        {
            res += button_attachments_form(tfield, tprops, tparams, lijst, requiredfield, true);
        }
    }
    else
    {
        var res = "";
        res += button_attachments_form(tfield, tprops, tparams, lijst, requiredfield);
    }

    return { html: res, count: lijst.length, addPreview: anyPreview, kotherkey: (lijst.length ? lijst[0].kmkey : -1) };
}

function button_attachments_form(tfield, tprops, tparams, lijst, requiredfield, appendlist)
{
    if (tfield.kdimensie == "xxx")
        var url = rooturl + "/appl/" + (tprops.mobile ? "pda" : "shared") + "/BijlagenSignature.asp"
    else
        var url = rooturl + "/appl/shared/BijlagenForm.asp"
    url += "?module=" + tparams.module
         + "&key=" + (tprops.copy ? -1 : tparams.pkey)
         + (tprops.niveau != ""? "&niveau=" + tprops.niveau : "")
         + "&kenmerk_key=" + tfield.kkey
         + (tprops.kenmerk_module? "&kenmerk_module=" + tprops.kenmerk_module : "");
    if (tfield.kdimensie)
        url += "&extFilter=" + escape(tfield.kdimensie);
    if (tfield.kregexp)
        url += "&pregexp=" + escape(tfield.kregexp); // P800x600 voor foto van 800x600
    if (tfield.kkenmerktype == "E")
        url += "&encrypt=1";
    if (tprops.copy || tparams.pkey < 0)
        url += "&tmpfolder=" + tprops.tmpfolder;
    if (tfield.kkenmerktype == "M") {
        if (tfield.kdimensie != "xxx") {
            url += "&multi=1";
        }
        if (tfield.readonlyfield) {
            url += "&reado=1";
        }
    }

    // De saveUrl
    if (tprops.parentKey > 0 && tfield.kkenmerktype != "M") // ![M]ap, dus [E]ncrypted of [F]ile
    {   // Voor de zekerheid slaan we wijziging van de bestandsnaam direct op.
        // op het filesysteem is het namelijk ook al 'gecommit'
        tfield.saveUrl = rooturl + "/appl/shared/AjaxSaveFlex.asp";
        if (tparams.module=="MLD" && tprops.niveau == "O")
            tfield.saveUrl += "?module=OPDR";
        else if (tparams.module=="FIN" && tprops.niveau == "R")
            tfield.saveUrl += "?module=FINR";
        else if (tparams.module=="RES" && tprops.extraserie) // Verbruiksartikelkenmerken
            tfield.saveUrl += "?module=RESA";
        else
            tfield.saveUrl += "?module=" + tparams.module;
        tfield.saveUrl += "&parentKey=" + tprops.parentKey;
        tfield.saveUrl += "&kenmerkKey=" + tfield.kkey;
        if (tprops.kenmerk_module)
            tfield.saveUrl += "&kenmerk_module=" + tprops.kenmerk_module;
    }

    // De aan te roepen functie incl. argumenten
    var func = tprops.mobile ? "onBijlagenMobile" : "onBijlagen"
    var blg = func + "('" + safe.jsstring(protectQS.create(url)) + "'"
            + ",'" + (tfield.saveUrl ? safe.jsstring(protectQS.create(tfield.saveUrl)) : "") + "'"
            + ", " + (tfield.kkenmerktype == "M" ? "true" : "false") // multi
            + ", $('#" + tprops.nameprefix + tfield.idCounter + "val')[0]"
            + ");";

    var res = "";

    // Handtekening plaatje
    if (tfield.kdimensie == "xxx") {
        res += "<img alt='" + safe.htmlattr(L("lcl_flex_preview")) + "' id='preview_" + tprops.nameprefix + tfield.idCounter + "' class='preview kenmerk'"
             + " onClick='" + safe.htmlattr(blg) + "'";
        if (lijst.length) {
            res += " src=' " + protectQS.create(OpenFlexFile(tparams.module, lijst[0].key, { mime_type: "image/png", contentdp: "inline" }));
        }
        res += "'>";
    }

    // Tekst op de button
    var btn_descr = L("lcl_add");
    if (tfield.kkenmerktype == "M") {
        btn_descr = L("lcl_appendixes_teller").format((appendlist ? 0 : lijst.length));
    } else if (tfield.kdimensie != "xxx" && lijst.length) {
        btn_descr = lijst[0].name;
        if (btn_descr.length > 28) { // Dit past zo'n beetje
            btn_descr = btn_descr.slice(0, 12)
                    + "..."
                    + btn_descr.slice(-12);
        }
    }

    // Icoontje op de button
    var btn_icon;
    if (tfield.kdimensie == "xxx") {
        btn_icon = I("fa-file-signature");
    } else if (tfield.kkenmerktype == "M") {
        btn_icon = I("fa-folder-open");
    } else if (tfield.kkenmerktype == "F") {
        btn_icon = I("fa-file");
    } else if (tfield.kkenmerktype == "E") {
        btn_icon = I("fa-file-lock");
    }

    // De button
    if (tprops.mobile) { // Alleen handtekening doen we hier, andere mobile bijlagen gaan via mobileBijlagen()
        var cls = "bijlage1 button signature";
        cls += (tfield.isSqlExpr ? " flexsql" : "")
        cls += (tfield.isExpression ? " expression " + (tfield.isShowExpr ? "flexshow" : "flexexpr") : "")
        cls += (tfield.required ? tparams.required_class : "");
        res += BUTTON_HTML(btn_descr, {
            "icon": "fa-file-signature",
            "id": tprops.nameprefix + tfield.idCounter + "val",
            "cls": cls,
            "click": blg,
            "attributes": {
                "isBijlage": "1",
                "nBijlagen": lijst.length,
                "aria-controls": "preview_" + tprops.nameprefix + tfield.idCounter
            }
        });
        res = "<div>" + res + "</div>"; // Wrap in divje om conditionele zichtbaarheid makkelijker te kunnen togglen
    } else {
        res += "<span class='flex-bijlage button-icon-wrapper'>"
            +   "<input type='button' isBijlage='1' nBijlagen='" + lijst.length + "' "
            +   " id='" + tprops.nameprefix + tfield.idCounter + "val'"
            +   " class='button " + (tfield.isExpression? " expression " + (tfield.isShowExpr ? "flexshow" : "flexexpr") : "")
            +            (tfield.kdimensie == "xxx" ? " signature" : "") + " " + requiredfield + "'"
            +   " value='" + safe.htmlattr(btn_descr) + "'"
            +   " onClick='" + safe.htmlattr(blg) + "'"
            +   (!tfield.readonlyfield ? " onChange='checkKenmerk(this, false,\"" + tfield.kkenmerktype + "\",0,0,0,0)'" : "") // Bij bijlage button heb je geen requiredbyemptyval nodig.
            +   ">"
            +     "<span class='endicon'>" + btn_icon + "</span>"
            + "</span>";
    }

    // Hidden field to store filename in kenmerk-table (F/E only)
    if (!tfield.saveUrl && tfield.kkenmerktype != "M") { // saveUrl doet dit zelf al
        res += "<input type='hidden' name='" + tprops.nameprefix + tfield.idCounter + "val' value='" + (lijst.length ? lijst[0].name : "") + "'>";
    }
    return res;
}

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;
}

// Evaluate an expression, returns evaluation outcome
var evaluateExpression = function (key, value, module, niveau)
{
    var index;
    var arr_match;
    var evaluation = false;
    var arr_flexfields;
    var arr_words;
    var arr_expression = [];
    var expr_type = 0;
    if (module !== "ALG" && module !== "PRS") {
        niveau = null;
    }
    if (!!value && value.indexOf("##SHOW## ") != -1) {
        expr_type = 1;
        arr_expression = value.split("##SHOW## ");
    } else if(!!value && value.indexOf("##EXPR## ") != -1) {
        expr_type = 2;
        arr_expression = value.split("##EXPR## ");
    }
    // no expression present
    if (arr_expression.length == 0) {
        return true;
    }
    // a length other then 2 is false
    if (arr_expression.length != 2)
    {
        return false;
    }
    // split the expression by :flex
    arr_flexfields = arr_expression[1].split(/:flex/i);
    for (index=1; index < arr_flexfields.length; index++)
    {
        // Replace the :flex pointer with a select statement
        arr_match = arr_flexfields[index].match(/[^0-9]/);
        if (arr_match) {
            arr_words = arr_flexfields[index].split(arr_match[0]);
        } else { // flex key value only
            arr_words = [arr_flexfields[index]];
        }

        // Retrieve the actual values
        arr_words[0] = "(SELECT flx.getflex(" + safe.quoted_sql(module) + ", " + arr_words[0] + ", " + key + ", " + safe.quoted_sql(niveau) + ") FROM DUAL)";

        // Recombine the condition where each flex pointer is replaced with a SELECT statement(s)
        if (arr_match) {
            arr_flexfields[index] = arr_words.join(arr_match[0]);
        } else {
            arr_flexfields[index] = arr_words[0];
        }
    }
    var sql_flex;
    if (expr_type == 1) { // ##SHOW##, we willen gewoon 0 of 1 terug
        sql_flex = "SELECT CASE"
                 + "         WHEN NVL (TO_CHAR(" + arr_flexfields.join("") + "), '0') = '0'"
                 + "         THEN 0"
                 + "         ELSE 1"
                 + "       END value"
                 + "  FROM dual";
    } else { // ##EXPR##
        sql_flex = "SELECT (" + arr_flexfields.join("") + ") value FROM dual";
    }
    var oRs_flex = Oracle.Execute(sql_flex);
    if (!oRs_flex.Eof) {
        evaluation = oRs_flex("value").Value;
    }
    oRs_flex.Close();
    return evaluation;
}

// Formatteert de bepaalde flexwaarde tot een html-fragment
// ik wil eigenlijk een structuur die het mobile ook net zo doet, of minstens goed nadenken en hier beschrijven wat de structuren zijn
function kv2html(pfield, pparams)
{
    var kvhtml = "";

    // Bloktitel's, behandelbare bloktitels's en Nieuwe secties doen we niks meer aan, die zijn goed zoals ze zijn.
    if (pfield.kkenmerktype == "l" || pfield.kkenmerktype == "b" || pfield.kkenmerktype == "Q")
        return pparams.kv;  // hidden when ishidden

    if (pparams.kv != "")
    {
        // Schrijf eventueel een tabelregel begin
        if (((pparams.colCount&1) || pparams.forceNewLine || !pparams.kenmerk_2col) && !pparams.props.mobile && !pparams.props.notr)
        {
            var cls = "trlabel {0}flex{1} ".format(pparams.module, pfield.kvolgnr);
            if (pfield.kkenmerktype == "L")
                cls += "flexlabel";
            if (pfield.ishidden ||
                (pparams.isParent && pparams.props.reado && (!pfield.kwaarde || pfield.kwaarde == ""))) // Hide een leeg parent kenmerk in de show mode (RWSN#68678).
                cls += " hidden";
            kvhtml += ("<tr" + (pfield.kkenmerktype == "L"
                               ? ' id="'+pparams.props.nameprefix+'clabel' + pparams.clabelCounter + '"'
                               : ''
                               )
                    + ' class="' + cls + '"'
                    + ">"); // Nu altijd zichtbaar in popup dmodal scherm
        }

        if (pparams.props.wfbuilder) // Workflow Expression Builder
            kvhtml += ("<td>:flex{0}</td>".format(pfield.kkey));

        var hashint = (pfield.khint ? " hashint" : "");
        var haspreview = (pparams.addPreview ? " haspreview" : "");
        var nohint = (pfield.kdefault ? "" : " nohint");

        if (!pparams.props.mobile && pparams.props.multiEdit)
        {
            var pid1 = pparams.props.nameprefix + pfield.idCounter;
            var pid2 = (pfield.kkenmerktype == "S" ? "Suggest" + pid1 + "_show" : pid1 + "val");
            m_check = "<td class='multi_cb'>"
                    + "<input id='mcb_" + pid1 + "val' type='checkbox' name='mcb_" + pid1 + "val' data-name='" + pid2 + "' req_m_" + pid2 + "='false' onchange='multi_change(this);'>"
                    + "</td>";
            kvhtml += m_check;
        }
        if (!pparams.props.mobile && !pparams.props.nolabel)
            kvhtml += ('\n  <td class="label' + hashint + haspreview + nohint
                    + (pfield.kkenmerktype == 'L' ? ' flexlabel' : '')
                    + ((pfield.kkenmerktype == 'C' && pfield.klengte > 50 && !pparams.props.kenmerk_search && !pfield.maskeren) ? ' textarea' : '') // readonly mag van mij ook wel
                    + (pparams.props.kenmerk_search? ' flexsearch' : '') + '"'
                    + (pparams.props.label_colspan? ' colspan=' + pparams.props.label_colspan : '') + '>');

        if (!pparams.props.nolabel && !pfield.ishidden)
        {
            if (S("fac_html_strictness") == 0)
                var safe_oms = pfield.komschrijving;
            else
                var safe_oms = safe.html(pfield.komschrijving); // strict mode

            if (pfield.maskeren) {
                safe_oms = I("fa-ban") + "&nbsp;" + safe_oms;
            }

            kvhtml += ('\n <label for="' + pparams.props.nameprefix + pfield.idCounter + 'val"'
                    + (pfield.krolcode ? ' data-fclt-rolcode="' + pfield.krolcode + '"' : '')
                    + (pparams.props.mobile && pfield.kkenmerktype == "L" ? ' class="h4"' : '')
                    + '>' + safe_oms + (pparams.label_ext ? " "+pparams.label_ext : "") + pfield.separator
                    + (
                        (pfield.kdimensie != null && pfield.kdimensie != "xxx")
                            ? " [" + pfield.kdimensie + "]"
                            : ""
                    )
                    + (pparams.props.mobile ? '' : '</label>') // info and preview icon inside label for mobile
                    + '<span class="'
                    + (pparams.addPreview ? 'labelextra details' : '')
                    + (pfield.isSqlExpr ? ' flexsql' : '')
                    + (pfield.isExpression ? ' expression ' + (pfield.isShowExpr ? "flexshow" : "flexexpr") : '') + '">'
                    + (pparams.addPreview ? '<span class="preview" id="p'+pfield.idCounter+'" title="'+L("lcl_flex_preview")+'">' + I("fa-image fa-lg") + '</span>' : '')
                    + (pfield.khint == null ? '' : '<span class="hint" '
                        + (pparams.props.mobile ? 'onClick="FcltMgr.alert(\'' + safe.jsstring(pfield.khint) + '\')"' : 'data-title="' + safe.htmlattr(pfield.khint) + '"') + '>' + I("fa-question-circle") + '</span>')
                    + '</span>'
                    + (pparams.props.mobile ? '</label>' : '')); // info and preview icon inside label for mobile
        }
        // labelwaardes (vaak invulinstructie) in apart te stylen span
        if (!pfield.ishidden && pparams.props.mobile && pfield.kkenmerktype == "L" && pparams.kv)
        {
            kvhtml += '\n  <span id="' + pparams.props.nameprefix + pfield.idCounter + 'val"'
                    + ' class="flexlabelvalue' + (pfield.isExpression ? ' expression ' + (pfield.isShowExpr ? "flexshow" : "flexexpr") : '') + '">'
                    + pparams.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 (!pparams.props.mobile)
        {
            if (!pparams.props.nolabel)
                kvhtml += ('</td>');

            if (pparams.props.kenmerk_search && pfield.kkenmerktype != "L")
            {
                kvhtml += ('<td class="operand_td">');
                kvhtml += (Operand(pfield.idCounter));
                kvhtml += ('</td>');
            }

            kvhtml += ('\n  <td ' + (pfield.kkenmerktype == "L"
                                        ? ' class="flexlabel lhint"'
                                        : (pfield.readonlyfield ? ' class="readonly tabindex=-1 flexvalue"' : ' class="flexvalue"')
                                    ));

            if (!pparams.props.kenmerk_search && pfield.kkenmerktype == "L"
                && typeof pparams.props.kenmerk_extraTD != "undefined" && pparams.props.kenmerk_extraTD)
                kvhtml += (' colspan="2">'); // MLD_OPDR heeft TD-tje extra
            else
                kvhtml += (">");

            if (pparams.props.nolabel && !pfield.ishidden)
                kvhtml += ('\n  <label for="' + pparams.props.nameprefix + pfield.idCounter + 'val">'
                       + '<span title="' + safe.htmlattr(pfield.khint) + '">'
                       + '</span></label>');
        }

        // Nu het eerder bepaalde waarde veld, zo nodig onderdrukt bij confidential-situatie
        if (!pfield.ishidden && pfield.maskeren)
        { // nu verstop ik alles wat we hierboven bepaald hebben. Dat is wat lomp.
            kvhtml += ("<span class='confidential'>"+ /* c_maskmarker + */ L("lcl_confidential")+"</span>");
            if (pfield.kwaarde && (pfield.kkenmerktype == "F" || pfield.kkenmerktype == "E"))
              kvhtml += "<input type='hidden' name='"+pparams.props.nameprefix + pfield.idCounter+"val' id='"+pparams.props.nameprefix + pfield.idCounter+"val' value='" + safe.htmlattr(pfield.kwaarde) + "'>";
        }
        else if (pparams.props.mobile && pfield.ishidden)
        {
            kvhtml += ("<span style='display: none'>" + pparams.kv + "</span>") || "";
        } else if (
            pparams.props.mobile &&
            !pfield.readonlyfield && !pfield.maskeren &&
            pfield.kkenmerktype == "V"
        ) {
            kvhtml = '<div class="flex-checkbox' + MOBILE_UI_CLASSES.checkbox_wrapper + '">' + kvhtml + pparams.kv + '</div>';
        } else
            kvhtml += (pparams.props.mobile && pfield.kkenmerktype == "L") ? "" : pparams.kv || "";

        if (typeof pparams.props.kenmerk_extraTD != "undefined" && pparams.props.kenmerk_extraTD && !pparams.props.mobile)
            kvhtml += '\n  </td><td>'; // Extra leeg celletje voor layout MLD_OPDR

        if (!pfield.maskeren) {
            if (!inArray(pfield.kkenmerktype, ["M", "L"]) &&
                (   (pfield.kwaarde && pfield.forceReadonly) /* ik denk dat readonlyfield ipv forceReadonly gebruikt moet worden */
                 || (pfield.usedInExpr && pfield.readonlyfield))) // (De waarde van) dit veld wordt in een expressie gebruikt
            {
                // hidden veld met de *echte* waardes zodat ze wel worden opgeslagen
                kvhtml += "\n  <input type='hidden' id='" + pparams.props.nameprefix + pfield.idCounter + "val' name='" + pparams.props.nameprefix + pfield.idCounter + "val' value='" + safe.htmlattr(pfield.kwaarde) + "'>";
            }

            if  (   pparams.props.kenmerk_search || !pfield.readonlyfield || (pfield.kwaarde && pfield.forceReadonly) /* ik denk dat readonlyfield ipv forceReadonly gebruikt moet worden */
                 || pfield.usedInExpr && pfield.readonlyfield) // (De waarde van) dit veld wordt in een expressie gebruikt
            {
                // Alleen dan hidden name-velden er bij (Die van bloktitel worden in de functie zelf toegevoegd)
                kvhtml += "\n<input type='hidden' name='"+pparams.props.nameprefix + pfield.idCounter+"key' id='"+pparams.props.nameprefix + pfield.idCounter+"key' value='"+pfield.kkey+"'>";
                kvhtml += "\n<input type='hidden' name='"+pparams.props.nameprefix + pfield.idCounter+"count' id='"+pparams.props.nameprefix + pfield.idCounter+"count' value='"+pparams.kcount+"'>";
                if (pparams.kcount > 1)
                    kvhtml += "\n<input type='hidden' name='"+pparams.props.nameprefix + pfield.idCounter+"omsch' id='"+pparams.props.nameprefix + pfield.idCounter+"omsch' value='"+pfield.komschrijving+"'>";
                kvhtml += "\n<input type='hidden' name='"+pparams.props.nameprefix + pfield.idCounter+"t'   id='"+pparams.props.nameprefix + pfield.idCounter+"t'   value='"+pfield.kkenmerktype+"'>";
                if (pfield.kkenmerktype == "F" || pfield.kkenmerktype == "E")
                {
                    kvhtml += "\n<input type='hidden' name='"+pparams.props.nameprefix + pfield.idCounter+"other_key' id='"+pparams.props.nameprefix + pfield.idCounter+"other_key' value='"+(pparams.kotherkey || -1)+"'>";
                }
                if (pfield.kkenmerktype == "C" || pfield.kkenmerktype == "N")
                {
                    kvhtml += "<input type='hidden' name='"+pparams.props.nameprefix + pfield.idCounter+"p'   id='"+pparams.props.nameprefix + pfield.idCounter+"p'   value='"+pfield.klengte+"'>";
                    if (pfield.kkenmerktype == "N")
                    {
                        kvhtml += "<input type='hidden' name='"+pparams.props.nameprefix + pfield.idCounter+"d'   id='"+pparams.props.nameprefix + pfield.idCounter+"d'   value='"+pfield.kdec+"'>";
                        if (pfield.kdec > 0)
                        {
                            if (pfield.knmin)
                            kvhtml += "<input type='hidden' name='"+pparams.props.nameprefix + pfield.idCounter+"min'   id='"+pparams.props.nameprefix + pfield.idCounter+"min'   value='"+pfield.knmin+"'>";
                            if (pfield.knmax)
                            kvhtml += "<input type='hidden' name='"+pparams.props.nameprefix + pfield.idCounter+"max'   id='"+pparams.props.nameprefix + pfield.idCounter+"max'   value='"+pfield.knmax+"'>";
                        }
                    }
                }
                if (pfield.isExpression)
                {
                    kvhtml += "<input type='hidden' name='"+pparams.props.nameprefix + pfield.idCounter+"e' id='"+pparams.props.nameprefix + pfield.idCounter+"e' value='"+ (pfield.isShowExpr ? 2 : 1) +"'>";
                }
            }
        }

        if (!pparams.props.mobile)
        {
            kvhtml += '</td>';
            if (!((pparams.colCount&1) && pparams.kenmerk_2col) && !pparams.props.notr)
                kvhtml += '\n</tr>';
        }

        /* Verplicht bij (sub)status afgerond moeten we clientside kunnen togglen */
        if (pfield.reqbystatus == "52" && !inArray(pfield.kkenmerktype, ["L", "l", "b", "Q"]) && !pfield.readonlyfield && !pfield.maskeren)
        {
            var jQuery_selector = "#" + (pfield.kkenmerktype == "S" ? "Suggest" : "")
                                + pparams.props.nameprefix + pfield.idCounter
                                + (pfield.kkenmerktype == "S" ? "_show" : "val");
            kvhtml += '<script>$("' + jQuery_selector + '").attr("data-required-completed", 1);</script>';
        }
    }
    return kvhtml;
}


// libraryfunctie
function refvaluessql (tfield, tparams)
{
    // The result is *the SQL statement* with 4 columns to optain:
    // - the list of all (current) values, or
    // - the current value
    // in column order rkey, romschr, rvervaldatum, rxmlnode
    // the rsxmlnode column is a convenient way to pass the knowledge to teh caller

    var fsql ="";
    var wheres = [];
    var dsql = "SELECT fac_kenmerkdomein_omschrijving"
             + "     , 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 = " + tfield.kdomein_key;
    var oRs_d       = Oracle.Execute(dsql);
    var xmlnode     = oRs_d("fac_kenmerkdomein_xmlnode").Value;
    var vervalkolom = oRs_d("fac_kenmerkdomein_verval").Value;

    //@@TODO deze wheres worden volgens mij niet gebruikt! Zou dat wel moeten??
    if (tparams.parentkenmerkid != "")
    {
      if (tparams.parentkenmerkwaarde != null)
        wheres.push("fac_usrdata_parentkey = " + tparams.parentkenmerkwaarde);
      else // Parent heeft geen waarde geselecteerd.
        wheres.push("fac_usrdata_parentkey IS NOT NULL");
    }

    var lflexkenmerkwaarde = tfield.kwaarde;
    if (lflexkenmerkwaarde !== null && String(oRs_d("fac_kenmerkdomein_kolomnaam").Value).match(/\_key$/i))
    {
        var vv = parseInt(lflexkenmerkwaarde, 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
            // We negeren het gewoon stil, het vervuilt mij de logging te veel
            __Log("Ongeldige referentie waarde <em>{0}</em> voor kolom {1}.{2}".format(tfield.kwaarde,
                                      oRs_d("fac_kenmerkdomein_objectnaam").Value,
                                      oRs_d("fac_kenmerkdomein_kolomnaam").Value), "#ff0000");
            lflexkenmerkwaarde = "-1";
        }
        else
            lflexkenmerkwaarde = String(vv);
    }

    if (oRs_d("fac_kenmerkdomein_objectnaam").Value == "FAC_USRDATA" && !oRs_d("fac_usrtab_key").Value)
    {
        shared.internal_error("Missing usertable for '{0}' {1}".format(L("lcl_kenmerkdomein"), oRs_d("fac_kenmerkdomein_omschrijving").Value));
    }

    if (oRs_d("fac_kenmerkdomein_objectnaam").Value == "FAC_USRDATA")
    {
        // Een verwijzing uit een usertabel
        // Deze kunnen we standaard localisen!
          fsql = "SELECT fac_usrdata_key rkey"
               + "     , " + lcl.xsql('fac_usrdata_omschr', 'fac_usrdata_key') + " romschr"
               + "     , fac_usrdata_vervaldatum rvervaldatum"
               + ", " + (xmlnode ? safe.quoted_sql(xmlnode) : "NULL") + " rxmlnode"
               + "     , fac_usrdata_volgnr"
               + "  FROM fac_usrdata "
               + " WHERE fac_usrtab_key=" + oRs_d("fac_usrtab_key").Value
               + (!tfield.readonlyfield
                 ? " AND fac_usrdata_verwijder IS NULL"
                 : " AND fac_usrdata_key = " + lflexkenmerkwaarde)
               + "   AND (fac_usrdata_vervaldatum IS NULL OR fac_usrdata_vervaldatum > SYSDATE)"
               + (tfield.kregexp?" AND fac_usrdata_key IN ({0})".format(safe.int_array(tfield.kregexp.split(",")).join(",")):"")
               + " UNION "
               + "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"
               + ", " + (xmlnode ? safe.quoted_sql(xmlnode) : "NULL") + " rxmlnode"
               + "     , fac_usrdata_volgnr"
               + "  FROM fac_usrdata "
               + " WHERE fac_usrtab_key = " + oRs_d("fac_usrtab_key").Value
               + "   AND fac_usrdata_key = " + lflexkenmerkwaarde;
    }
    else
    {
      // Een verwijzing uit een andere view of tabel
      // var wheres = [];  // DIT LIJKT ME STERK. Het wist de afhankelijkheid van parentwaarden
      if (vervalkolom)
        wheres.push("(" + vervalkolom + " IS NULL OR " + vervalkolom + " > SYSDATE)");
      if (tfield.readonlyfield)
        wheres.push(oRs_d("fac_kenmerkdomein_kolomnaam").Value + " = " + safe.quoted_sql(tfield.kwaarde)); // niet die lflexkenmerkwaarde?

      fsql = "SELECT " + oRs_d("fac_kenmerkdomein_kolomnaam").Value + " rkey, "
           + oRs_d("fac_kenmerkdomein_kolomtxt").Value + " romschr,"
           + (vervalkolom ? vervalkolom : "NULL") + " rvervaldatum,"
           + (xmlnode ? safe.quoted_sql(xmlnode) : "NULL") + " rxmlnode"
           + "  FROM " + oRs_d("fac_kenmerkdomein_objectnaam").Value
           + (wheres.length?" WHERE " + wheres.join(" AND "):"")
           + " UNION " // De huidige waarde altijd. Doordat hier *geen* UNION ALL staat ontstaan er geen dubbelen
           + "SELECT " + oRs_d("fac_kenmerkdomein_kolomnaam").Value + " rkey, "
           + oRs_d("fac_kenmerkdomein_kolomtxt").Value
               + (vervalkolom
                   ? " || CASE WHEN  " + vervalkolom + " < SYSDATE THEN " + safe.quoted_sql(L("lcl_inactive_data_suffix")) + " END"
                   : ""
               ) + " romschr, "
           + (vervalkolom ? vervalkolom : "NULL") + " rvervaldatum,"
           + (xmlnode ? safe.quoted_sql(xmlnode) : "NULL") + " rxmlnode"
           + "  FROM " + oRs_d("fac_kenmerkdomein_objectnaam").Value
           + " WHERE " + oRs_d("fac_kenmerkdomein_kolomnaam").Value + " = " + safe.quoted_sql(lflexkenmerkwaarde);
    }


    fsql += (oRs_d("fac_kenmerkdomein_objectnaam").Value == "FAC_USRDATA")
           ? " ORDER BY fac_usrdata_volgnr, 2"
           : " ORDER BY 2, 1";
    oRs_d.close();

    return fsql;
}




// ==========================================================================================
// The functions to return the value-html for each field type
// ==========================================================================================

// ==========================================================================================
function kenmerktype_text (pfield, pprops, pparams)
{
    var lkv = "";
    if (pfield.klengte > 50 && !pprops.kenmerk_search)
    {
        // Groot text veld wordt automatisch textarea
        if (pfield.readonlyfield || pfield.maskeren)
        {
            if (pprops.mobile)
            {
                lkv = "<textarea rows=3 readonly class='" + MOBILE_UI_CLASSES.textarea_plain + "' tabindex=-1 maxlength='"+pfield.klengte+"'>" + safe.textarea(pfield.kwaarde) + "</textarea>";
            }
            else
            {
                lkv = "<div class='readonly'>" + safe.html(pfield.kwaarde) + "</div>";
            }
        }
        else
        {
            lkv = "<textarea "
                + (pfield.kregexp ? " regexp='" + pfield.kregexp + "' " : "")
                + " onChange='fvalid=checkKenmerk(this, false,\"" + pfield.kkenmerktype + "\"," + pfield.klengte + "," + pfield.kdec + "," + pfield.knmin + "," + pfield.knmax + ", " + (pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null") + ")"
                + (pfield.usedInExpr ? pparams.onchangeExpValid : "")
                + "; " + (pprops.multiEdit ? "multi_fld_change(this);" : "") + "' "
                + " onBlur='checkKenmerk(this, true,\"" + pfield.kkenmerktype + "\"," + pfield.klengte + "," + pfield.kdec + "," + pfield.knmin + "," + pfield.knmax + ", " + (pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null") + ")'"
                + " class='fldtxt fldflexC "
                + (pfield.required ? pparams.required_class : "")
                + (pfield.isSqlExpr ? ' flexsql' : '')
                + (pfield.isExpression ? " expression " + (pfield.isShowExpr ? "flexshow" : "flexexpr") : "")
                + (pprops.mobile ? MOBILE_UI_CLASSES.textarea : "")
                + "'"
                + (pfield.required ? " required='required' " : "")
                + (pfield.isReadonly ? " readonly tabindex=-1 " : "")
                + " rows=3 "
                + " maxlength='"+pfield.klengte+"'"
                + " name='" + pprops.nameprefix + pfield.idCounter + "val' id='" + pprops.nameprefix + pfield.idCounter + "val' "
                //+ "placeholder='"+pfield.komschrijving+"'"
                + ">"
                + safe.textarea(pfield.kwaarde)
                + "</textarea>";
        }
    }
    else
    { // klein input veld
        if (pfield.readonlyfield || pfield.maskeren)
        {
            if (pprops.mobile)
            {
                lkv = "<input type='text' readonly class='" + MOBILE_UI_CLASSES.input_plain + "' tabindex=-1 value='"+ safe.html(pfield.kwaarde) +"'>";
            }
            else
            {
                lkv = "<span>" + safe.htmlattr(pfield.kwaarde) + "</span>";
            }
        }
        else
        {
            lkv = "<input type='text'"
                + (pfield.kregexp ? " regexp='" + pfield.kregexp + "' " : "")
                + " onChange='fvalid=checkKenmerk(this, false,\"" + pfield.kkenmerktype + "\"," + pfield.klengte + "," + pfield.kdec + "," + pfield.knmin + "," + pfield.knmax + ", " + (pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null") + ")"
                + (pfield.usedInExpr ? pparams.onchangeExpValid : "")
                + "; " + (pprops.multiEdit ? "multi_fld_change(this);" : "") + "' "
                + " onBlur='checkKenmerk(this, true,\"" + pfield.kkenmerktype + "\"," + pfield.klengte + "," + pfield.kdec + "," + pfield.knmin + "," + pfield.knmax + ", " + (pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null") + ")'"
                + " id='" + pprops.nameprefix + pfield.idCounter + "val' name='" + pprops.nameprefix + pfield.idCounter + "val'"
                + " class='fld fldflexC "
                + (pfield.required ? pparams.required_class : "")
                + (pfield.isSqlExpr ? " flexsql" : "")
                + (pfield.isExpression ? " expression " + (pfield.isShowExpr ? "flexshow" : "flexexpr") : "")
                + (pprops.mobile ? MOBILE_UI_CLASSES.input : "")
                + "'"
                + (pfield.required ? " required='required' " : "")
                + (pfield.isReadonly ? " readonly tabindex=-1 " : "")
                //+ " placeholder='"+pfield.komschrijving+"'"
                + " value='" + safe.htmlattr(pfield.kwaarde) + "'"
                + " maxlength='" + pfield.klengte + "'"
                + ">";
        }
    }
    if (pprops.mobile) {
        lkv = "<div>" + lkv + "</div>";
    }
    return lkv;
}

// ==========================================================================================
function kenmerktype_listbox (pfield, pprops, pparams)
{
    var lkv = "";
    var xmlnode;
    // Bepaal key, omschrijving, vervaldatum en evt xmlnode van de domeinwaarde(n)
    var fsql = refvaluessql(pfield, pparams);
    var oRs2 = Oracle.Execute(fsql);

    var refkeyfield   = "";
    var refvaluefield = "";
    var inactiveval   = false;
    var xmlnode;
    var radio = !pprops.kenmerk_search && !pprops.multiEdit && pfield.kkenmerktype === "r"; // Listbox of radiobuttons?
    if (!oRs2.eof)
    {
        refkeyfield   = oRs2("rkey").Value;
        refvaluefield = oRs2("romschr").Value;
        inactiveval   = (oRs2("rvervaldatum").Value != null && oRs2("rvervaldatum").Value < new Date());
        xmlnode       = oRs2("rxmlnode").Value;
    }

    if (pfield.readonlyfield || pfield.maskeren)
    {
        if (pprops.mobile)
            lkv = '<div><input class="fld fldflexR' + MOBILE_UI_CLASSES.input_plain
                + (pfield.isSqlExpr ? ' flexsql' : '')
                + (pfield.isExpression ? " expression " + (pfield.isShowExpr ? "flexshow" : "flexexpr") : "")
                + (inactiveval ? ' inactive' : '')
                + '" type="text" readonly tabindex=-1'
                + ' value="' + safe.htmlattr(refvaluefield) + '"></input></div>';
        else
            lkv = '<span class="fld fldflexR'
                + (xmlnode ? ' details' : '')
                + (pfield.isSqlExpr ? ' flexsql' : '')
                + (pfield.isExpression? " expression " + (pfield.isShowExpr ? "flexshow" : "flexexpr") : "")
                + (inactiveval? ' inactive' : '')
                + '" type="text" readonly tabindex=-1'
                + (xmlnode ? ' onClick="xmlNodeDetails(' + pfield.kwaarde + ', \'' + xmlnode + '\')"' : '')
                + '>' + safe.html(refvaluefield) + "</span>";
    }
    else
    {
        // 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 = "";
        var c = 0;

        while (!oRs2.eof)
        {
          refkeyfield   = oRs2("rkey").Value;
          refvaluefield = oRs2("romschr").Value;
          if (pfield.readonlyfield)
          {
            if (refkeyfield == pfield.kwaarde)
            {
              kvv = refvaluefield;
              break; // Rap klaar. Kan efficienter met een WHERE?
            }
          }
          else
          {
            if (refvaluefield != null && String(refvaluefield.length) > maxLen)
                maxLen = String(refvaluefield).length;
            if (radio) { // Radiobuttons
                appender = '<input type="radio" id="' + pprops.nameprefix + pfield.idCounter + "_" + c + '"'
                         + ' value="' + refkeyfield + '" name="' + pprops.nameprefix + pfield.idCounter + 'val"'
                         + ' class="fldflexr'
                         + (pfield.required ? pparams.required_class : "")
                         + (pfield.isSqlExpr ? ' flexsql' : '')
                         + (pfield.isExpression ? " expression " + (pfield.isShowExpr ? "flexshow" : "flexexpr") : "")
                         + (pprops.mobile ? MOBILE_UI_CLASSES.checkbox : "")
                         + '"'
                         + ' onChange="' + (pfield.usedInExpr ? pparams.onchangeExp : "") + '"'
                         + (refkeyfield == pfield.kwaarde?' checked="checked"' : '') + '>'
                         + '<label for="' + pprops.nameprefix + pfield.idCounter + "_" + c + '"'
                         + (pprops.mobile ? ' class="' + MOBILE_UI_CLASSES.checkbox_label + '"' : '')
                         + '>' + safe.html(refvaluefield) + '</label>';
                if (pprops.mobile) {
                    appender = '<div class="' + (pprops.mobile ? MOBILE_UI_CLASSES.checkbox_wrapper : '') + '">' + appender + '</div>';
                }
            } else { // Listbox
                appender = '<option value="' + refkeyfield + '"'
                         + (refkeyfield == pfield.kwaarde?' selected' : '') + '>' + safe.html(refvaluefield) + '</option>';
            }
            builder[builder.length] = appender;
          }
          oRs2.MoveNext();
          c++;
        }

        if (kvv == '') {
            if (!pprops.mobile && radio) {
                kvv += builder.join('<br>');
            } else {
                kvv += builder.join(' ');
            }
        }
        if (kvv == '') kvv = ' '; // Zodat readonly/print in ieder geval het label getoond wordt.
        //kvv is nu een waarde of een reeks opties voor een select

        if (radio) {
            lkv = kvv;
        } else {
            // is dus impliciet ook type R
            lkv = "<select id='" + pprops.nameprefix + pfield.idCounter + "val' name='" + pprops.nameprefix + pfield.idCounter + "val'"
                + " class='fld fldflexR "
                    + (pfield.required ? pparams.required_class : "")
                    + (pfield.isSqlExpr ? ' flexsql' : '')
                    + (pfield.isExpression? " expression " + (pfield.isShowExpr ? "flexshow" : "flexexpr") : "")
                    + (pprops.mobile ? MOBILE_UI_CLASSES.select : '')
                    + "'"
            + (pprops.kenmerk_search ? " advmulti=1 " : "")
            + (pfield.required ? " required='required' " : "")
            + (pfield.required > 1
                ? ( pprops.multiEdit
                  ? " onChange='multi_fld_change(this);'"
                  : " onChange='checkRequiredGroup(\"" + pparams.required_group
                                             + "\", "  + (pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null")
                                               + ");"
                                             + (pfield.usedInExpr ? pparams.onchangeExp + "; " : "")
                            + "' "
                  )
                : (pparams.hasAnyFlexExpr
                  ? " onChange='" + (pfield.usedInExpr ? pparams.onchangeExp : "")
                  + "; " + (pprops.multiEdit ? "multi_fld_change(this);" : "") + "' "
                  : (pprops.multiEdit ? "onChange='multi_fld_change(this);'" : "")
                  )
              )
                + " data-icon='fa-chevron-down'"
            + ">"
            + "<option value='-1' " + (pfield.required? " class='" + pparams.required_class + "'" : "") + "><!--lege waarde--></option>"
            + kvv
            + "</select>";
            if (pprops.mobile) {
                lkv = "<div>" + lkv + "</div>";
            }
        }

        if (!pfield.readonlyfield && pparams.parentkenmerkid != "" && !pfield.maskeren)
        { %>
          <script type="text/javascript">
            function onChangeRKenmerkParent<%=pparams.parentkenmerkid%>()
            { // Functie wordt aangeroepen door een eventuele parent waarvan deze listbox afhankelijk is
              var className = $("#<%=pparams.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 = $("#<%=pparams.parentkenmerkid%>val")[0].className.match(/required\d*S\d*/g)[0].substr(8);
                checkRequiredGroup(group, <%=(pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null")%>);
              }

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

            $(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.
              if ($("#<%=pparams.parentkenmerkid%>val").length)
              {
                $("#<%=pparams.parentkenmerkid%>val").change(function() { onChangeRKenmerkParent<%=pparams.parentkenmerkid%>(); });
                $("#<%=pparams.parentkenmerkid%>val").change();
              }
            });
          </script><%
        }
    }
    oRs2.close();
    return lkv;
}

// ==========================================================================================
function kenmerktype_suggest (pfield, pprops, pparams)
{
    var lkv = "";
    var lkenmerkwaarde = "";
    if (pfield.kwaarde)
    {
        var rsql = "SELECT flx.getdomeinwaarde("+pfield.kdomein_key+", "+safe.quoted_sql(pfield.kwaarde)+") refwaarde FROM DUAL";
        var rORs = Oracle.Execute(rsql);
        if (!rORs.eof)
        {
            lkenmerkwaarde = rORs("refwaarde").value;
        }
        else
        {
            lkenmerkwaarde = "???"; //error
        }
        rORs.close();
    }

    if (pfield.readonlyfield)
    {
        var xmlnode, refkeyfield;
        if (!pprops.mobile)
        {
            var fsql = refvaluessql(pfield, pparams);
            var oRs2 = Oracle.Execute(fsql);

            if (!oRs2.eof)
            {
                refkeyfield     = oRs2("rkey").Value;
                lkenmerkwaarde  = oRs2("romschr").Value;
                xmlnode         = oRs2("rxmlnode").Value;
            }
            oRs2.Close();

            lkv = "<span" + ((xmlnode && !pprops.mobile) ? " class='details'" : "")
                + ((xmlnode && !pprops.mobile) ? ' onClick="xmlNodeDetails(' + refkeyfield + ', \'' + xmlnode + '\')"' : '')
                + ">" + safe.html(lkenmerkwaarde) + "</span>";
        }
        else
            lkv = "<input type='text' class='fld" + MOBILE_UI_CLASSES.input_plain + "' readonly tabindex=-1"
                + " value='" + safe.htmlattr(lkenmerkwaarde) + "'></input>";
    }
    else
    {
%>              <script type="text/javascript">
<%              if (pparams.parentkenmerkid != "")
        {
%>
            function onChangeSKenmerkParent<%=pparams.parentkenmerkid%>(parentkey, parenttxt)
            { // Functie wordt aangeroepen door een eventuele parent waarvan deze suggest afhankelijk is
              // setValue(key, txt, doonChange, checkExist, extraParam, lastTry)
              var presentkey = $("#<%=pprops.nameprefix + pfield.idCounter%>val").val();
              var presenttxt = $("#Suggest<%=pprops.nameprefix + pfield.idCounter%>_show").val()
              Suggest<%=pprops.nameprefix + pfield.idCounter%>.setValue(presentkey, presenttxt, false, true, null, true);
              // Indien suggestbox verplicht is en maar één mogelijke waarde heeft, dan deze selecteren
              if ($("#Suggest<%=pprops.nameprefix + pfield.idCounter%>_show").hasClass("required")) Suggest<%=pprops.nameprefix + pfield.idCounter%>.CheckJustOne();

              var className = $("#Suggest<%=pparams.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<%=pparams.parentkenmerkid%>_show")[0].className.match(/required\d*S\d*/g)[0].substr(8);
                checkRequiredGroup(group, <%=(pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null")%>);
              }
            }
<%              }
        if (pfield.required)
        {
%>
            function onChangeSKenmerk<%=pprops.nameprefix + pfield.idCounter%>(parentkey, txt)
            {
              checkRequiredGroup("<%=pparams.required_group%>", <%=(pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null")%>);
            }
<%      }
%>
        $(function()
        {
            if ($("#execDone<%=pprops.nameprefix + pfield.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<%=pprops.nameprefix + pfield.idCounter%> =
                new Suggest({objectName: "Suggest<%=pprops.nameprefix + pfield.idCounter%>",
                            <%=(pprops.kenmerk_search ? "multitoggle: \"advanced\", " : "")%>
                             queryField: $("#Suggest<%=pprops.nameprefix + pfield.idCounter%>_show")[0],
                             queryUrl: rooturl + "/appl/shared/Suggest/SuggestKenmerkDomein.asp?kenmerkdomein_key=<%=pfield.kdomein_key%>",
                             initKey: '<%=pfield.kwaarde%>',
                             keyField: $("#<%=pprops.nameprefix + pfield.idCounter%>val")[0],
                             fieldReadonly: false
<%          if (typeof mobile != "undefined" && typeof modal != "undefined" && modal == 1)  // mobile en popup en kenmerken?
            { %>                                                                            // Dat komt alleen voor bij de 'reserveer werkplek' pagina,
                           , embedded: true                                                 // waar we een PDA pagina in een desktop popup gebruiken
<%          }
            if (pparams.parentkenmerkid != "")
            { %>
                           , urlAdd: [{ urlParam: "parent_key", field: "<%=pparams.parentkenmerkid%>val" }]
<%          } %>
                          });
<%            if (pparams.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<%=pparams.parentkenmerkid%>_show").attr("sgonChange", "onChangeSKenmerkParent<%=pparams.parentkenmerkid%>");
<%            }
%>
              $("#execDone<%=pprops.nameprefix + pfield.idCounter%>").val(1); // Het is nu 1 keer uitgevoerd. Nu niet meer uitvoeren.
<%            if (pparams.hasAnyFlexExpr)
              {
%>
                $("#Suggest<%=pprops.nameprefix+pfield.idCounter%>_show").attr("onblur", '<%=(pfield.usedInExpr ? pparams.onchangeExp : "")%>');
<%            }
              if (pprops.multiEdit) { %>
              $("#Suggest<%=pprops.nameprefix + pfield.idCounter%>_show").attr("sgonChange", "multi_fld_change");
<%            } %>
            }
        });
        </script>
<%
        lkv = "<input type='text' "
           + (pfield.kregexp ? " regexp='" + pfield.kregexp + "' " : "")
           + "id='Suggest" + pprops.nameprefix + pfield.idCounter + "_show' "
           + "name='Suggest" + pprops.nameprefix + pfield.idCounter + "_show' "
           + "value='" + safe.htmlattr(lkenmerkwaarde) + "' "
           + "class='fld fldflexS "
             + (pfield.isSqlExpr ? ' flexsql' : '')
             + (pfield.isExpression? "expression " + (pfield.isShowExpr ? "flexshow" : "flexexpr") : "")
             + (pfield.required ? " " + pparams.required_class : "")
             + (pprops.mobile ? MOBILE_UI_CLASSES.input : "")
             + "'"
           // zo wordt het veld rood, ongeacht de waarde, wat verwarrend is, dus niet + (inactiveval ? " inactive " : "")
           + (pfield.required ? " required='required' " : "")
           + (pfield.required > 1
             ? " sgonChange='onChangeSKenmerk" + pprops.nameprefix + pfield.idCounter + "'"
             : "")
           + ">"
           + "<input type='hidden' id='" + pprops.nameprefix + pfield.idCounter + "val' name='" + pprops.nameprefix + pfield.idCounter + "val'>"
           + "<input type='hidden' id='execDone" + pprops.nameprefix + pfield.idCounter + "' name='execDone" + pprops.nameprefix + pfield.idCounter + "' value='0'>";
    }
    if (pprops.mobile) {
        lkv = "<div class='input-icon-wrapper'>" + lkv + "</div>";
    }
    return lkv;
}

// ==========================================================================================
function kenmerktype_date (pfield, pprops, pparams, dpYearRanges)
{
    var lkv = "";
    if (pfield.readonlyfield)
    {
        if (pprops.mobile)
            lkv = "<div><input type='text' class='fld " + MOBILE_UI_CLASSES.input_plain + "' readonly tabindex=-1 value='" + safe.htmlattr(pfield.kwaarde) + "'></input></div>";
        else
            lkv = "<span>" + safe.html(pfield.kwaarde) + "</span>";

    }
    else
    {
        lkv  = "<span class='input-icon-wrapper'>";
        lkv += "<input type='text'"
             + (pfield.kregexp ? " regexp='" + pfield.kregexp + "'" : "")
             + " onChange='fvalid=checkKenmerk(this, false,\"" + pfield.kkenmerktype + "\"," + pfield.klengte + "," + pfield.kdec + "," + pfield.knmin + "," + pfield.knmax + ", "
                 + (pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null") + ")" + (pfield.usedInExpr ? pparams.onchangeExp : "")
                 + ";; " + (pprops.multiEdit ? "multi_fld_change(this);" : "") + "' "
             + " onBlur='checkKenmerk(this, true,\"" + pfield.kkenmerktype + "\"," + pfield.klengte + "," + pfield.kdec + "," + pfield.knmin + "," + pfield.knmax + ", " + (pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null") + ")'"
             + " id='" + pprops.nameprefix + pfield.idCounter + "val'"
             + " name='" + pprops.nameprefix + pfield.idCounter + "val' ";

        lkv += " class='flddate " /* deze bepaalt ook dat er een dateselector op komt */
             + (pfield.required ? " " + pparams.required_class : "")
             + (pfield.isSqlExpr ? ' flexsql' : '')
             + (pfield.isExpression ? " expression " + (pfield.isShowExpr ? "flexshow" : "flexexpr") : "")
             + (pprops.mobile ? MOBILE_UI_CLASSES.input : "")
             + "'"
             + (pfield.required ? " required='required' " : "")
             + " onChange='" + (pfield.usedInExpr ? pparams.onchangeExp : "") + "' "
             + " value='" + safe.htmlattr(pfield.kwaarde) + "'"
             + " autocomplete=\"off\""
             + ">";

        if ((!pfield.isExpression || pfield.isShowExpr) && !pfield.maskeren)
            lkv += "<span class=\"dateklikker\" onclick=\"$('#" + pprops.nameprefix + pfield.idCounter + "val').datepicker('show')\"> " + I("fa-calendar") + "</span>";
        lkv += "</span>";

        // Als er voor een datumveld geen knmin en knmax 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 (pfield.knmin || pfield.knmax)
        {
            range_val = (pfield.knmin ? ((Math.abs(pfield.knmin)<1000) ? "c"+(pfield.knmin>0?"+"+pfield.knmin:pfield.knmin) : Math.abs(pfield.knmin)) : "c-10") + ":"
                      + (pfield.knmax ? ((Math.abs(pfield.knmax)<1000) ? "c"+(pfield.knmax>0?"+"+pfield.knmax:pfield.knmax) : Math.abs(pfield.knmax)) : "c+10");
            field_id = pprops.nameprefix + pfield.idCounter + "val";
            dpYearRanges.push({id: field_id, range: range_val});
        }

    }
    return lkv;
}

// ==========================================================================================
function kenmerktype_time (pfield, pprops, pparams)
{
    var lkv= "";
    if (pfield.readonlyfield)
    {
        if (pprops.mobile)
            lkv = "<div><input type='text' class='fld " + MOBILE_UI_CLASSES.input_plain + "' readonly tabindex=-1 value='" + safe.htmlattr(pfield.kwaarde) + "'></input></div>";
        else
            lkv = "<span>" + safe.html(pfield.kwaarde) + "</span>";
    }
    else
    {   // Defaults
        var lkmin = "0";
        var lkmax = "23.99"; // Not 23.75 because you want to be able to manually enter 23:59

        if (pfield.knmin !== null)
            lkmin = Math.max(0,     (Math.round(pfield.knmin * 4) / 4)).toFixed(2); // Rounded to nearest quarter

        if (pfield.knmax !== null)
            lkmax = Math.min(23.99, (Math.round(pfield.knmax * 4) / 4)).toFixed(2); // Rounded to nearest quarter

        var kminH = Math.floor(lkmin);
        var kminM = Math.round(lkmin * 60 % 60, 10);
        var kmaxH = Math.floor(lkmax);
        var kmaxM = Math.round(lkmax * 60 % 60, 10);

        lkmin = pad(String(kminH), 2) + ":" + pad(String(kminM), 2);
        lkmax = pad(String(kmaxH), 2) + ":" + pad(String(kmaxM), 2);

        /*global*/minMax.push({namePrefix: pprops.nameprefix, id: pfield.idCounter, kmin: lkmin, kmax: lkmax});

        lkv = "<span class='input-icon-wrapper'>";
        lkv += "<input type='text'"
             + (pfield.kregexp ? " regexp='" + pfield.kregexp + "' " : "")
             +  " onChange='fvalid=checkKenmerk(this, false,\"" + pfield.kkenmerktype + "\"," + pfield.klengte + "," + pfield.kdec + ",\"" + lkmin + "\",\"" + lkmax + "\", " + (pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null") + ")"
                 + (pfield.usedInExpr ? pparams.onchangeExpValid : "")
                 + "; " + (pprops.multiEdit ? "multi_fld_change(this);" : "") + "' "
             + " onBlur='checkKenmerk(this, true,\"" + pfield.kkenmerktype + "\"," + pfield.klengte + "," + pfield.kdec + ",\"" + lkmin + "\",\"" + lkmax + "\", " + (pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null") + ")'"
             + (!pfield.maskeren
               ? " id='" + pprops.nameprefix + pfield.idCounter + "val' name='" + pprops.nameprefix + pfield.idCounter + "val' "
               : "")
             + " class='fldtime " /* deze bepaalt ook dat er een timeselector op komt */
              + (pfield.required ? " " + pparams.required_class : "")
              + (pfield.isSqlExpr ? ' flexsql' : '')
              + (pfield.isExpression ? " expression " + (pfield.isShowExpr ? "flexshow" : "flexexpr") : "")
              + (pprops.mobile ? MOBILE_UI_CLASSES.input : "")
              + "'"
             + (pfield.required ? " required='required' " : "")
             + " size=\"5\""
             + " value='" + safe.htmlattr(pfield.kwaarde) + "'"
             + " autocomplete=\"off\""
             + ">";

        if (!pfield.maskeren)
            lkv += "<span class=\"flex timeklikker\" onclick=\"$('#" + pprops.nameprefix + pfield.idCounter + "val').click(); event.stopPropagation()\">" + I("fa-clock") + "</span>";
        lkv += "</span>";

    }
    return lkv;
}

// ==========================================================================================
function kenmerktype_checkbox (pfield, pprops, pparams)
{
    var lkv = "";
    if (pfield.readonlyfield || pfield.maskeren)
    {
        if (pprops.mobile)
        {
            lkv = "<input type='text' class='fld" + MOBILE_UI_CLASSES.input_plain + "' readonly tabindex=-1 value='" + (pfield.kwaarde == 1? L("lcl_check_1") : L("lcl_check_0")) + "'>";
        }
        else
        {
            lkv = "<span class='fldcheck'>"
               + (pfield.kwaarde == 1? L("lcl_check_1") : L("lcl_check_0"))
               + "</span>";
        }
    }
    else
    {
        lkv = "<input type='checkbox' class='fldshort "
            + (pfield.required ? " " + pparams.required_class : "")
            + (pfield.isSqlExpr ? ' flexsql' : '')
            + (pfield.isExpression ? " expression " + (pfield.isShowExpr ? "flexshow" : "flexexpr") : "")
            + (pprops.mobile ? MOBILE_UI_CLASSES.checkbox : "")
            + "'"
            + " onChange='fvalid=checkKenmerk(this, false,\"" + pfield.kkenmerktype + "\"," + pfield.klengte + "," + pfield.kdec + "," + pfield.knmin + "," + pfield.knmax + ", " + (pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null") + ")"
            + (pfield.usedInExpr ? pparams.onchangeExpValid : "")
            + "; " + (pprops.multiEdit ? "multi_fld_change(this);" : "") + "' "
            + " onBlur='checkKenmerk(this, true,\"" + pfield.kkenmerktype + "\"," + pfield.klengte + "," + pfield.kdec + "," + pfield.knmin + "," + pfield.knmax + ", " + (pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null") + ")' "
            + (pfield.kwaarde == 1 ? " checked" : "")
            + " id='" + pprops.nameprefix + pfield.idCounter + "val'"
            + " name='" + pprops.nameprefix + pfield.idCounter + "val'"
            + ">"
            + "<input type='hidden' id='has_" + pprops.nameprefix + pfield.idCounter + "val' name='has_" + pprops.nameprefix + pfield.idCounter + "val' value='1'>";
    }
  return lkv;
}

// ==========================================================================================
function kenmerktype_number (pfield, pprops, pparams)
{
    var lkv = "";
    if (pfield.readonlyfield || pfield.maskeren)
    {
        if (pfield.kdec > 0 && pfield.kwaarde)
            var safeformattedvalue = safe.showFloat(pfield.kwaarde, pfield.kdec);
        else
            var safeformattedvalue = safe.html(pfield.kwaarde);

        if (pprops.mobile)
            lkv = "<input type='text' class='fldshort " + MOBILE_UI_CLASSES.input_plain + "' readonly tabindex=-1 value='"+ safeformattedvalue +"'>";
        else
            lkv = "<span>" + safeformattedvalue + "</span>";
    }
    else
    {
        var checkkenmerk_params = "null";
        if (pprops.requiredbyemptyval != null || pprops.relativeValuesAllowed)
        {
          var checkkenmerk_params = " {relativeValuesAllowed: " + (pprops.relativeValuesAllowed && pfield.kdec > 0? "true" : "false")
                                  + (pprops.requiredbyemptyval != null? ", emptyvalue: " + pprops.requiredbyemptyval : "")
                                  + "}";
        }
        lkv = "<input type='text'"
           + (pfield.kregexp ? " regexp='" + pfield.kregexp + "'" : "")
           + " onChange='fvalid=checkKenmerk(this, false,\"" + pfield.kkenmerktype + "\"," + pfield.klengte + "," + pfield.kdec + "," + pfield.knmin + "," + pfield.knmax + ", "
             + checkkenmerk_params + ")"
             + (pfield.usedInExpr ? pparams.onchangeExpValid : "") + ";"
             + (pprops.multiEdit ? " multi_fld_change(this);" : "") + "' "
           + " onBlur='checkKenmerk(this, true,\"" + pfield.kkenmerktype + "\"," + pfield.klengte + "," + pfield.kdec + "," + pfield.knmin + "," + pfield.knmax + ", "
           + checkkenmerk_params + ")'"
           + " id='" + pprops.nameprefix + pfield.idCounter + "val'"
           + " name='" + pprops.nameprefix + pfield.idCounter + "val' ";

        lkv += " class='fldshort "
            + (pfield.required ? pparams.required_class : "")
            + (pfield.kdec > 0 ? " float " : " number ")
            + (pfield.isSqlExpr ? " flexsql" : "")
            + (pfield.isExpression? " expression " + (pfield.isShowExpr ? "flexshow" : "flexexpr") : "")
            + (pfield.kdec > 0 && pprops.relativeValuesAllowed? " relativeValuesAllowed" : "")
            + (pprops.mobile ? MOBILE_UI_CLASSES.input : "")
            + "'"
            + (pfield.required ? " required='required' " : "")
            + (pfield.isReadonly ? " readonly tabindex=-1 " : "")
            + " value='" + (pfield.kdec > 0 && pfield.kwaarde? safe.displayfloat(parseFloat(pfield.kwaarde), pfield.kdec) : safe.htmlattr(pfield.kwaarde)) + "'"
            + " maxlength='" + ((pfield.knmin >= 0? 0 : 1) + (pfield.kdec > 0? 1 : 0) + pfield.klengte) + "'"
           + ">";
    }
    if (pprops.mobile) {
        lkv = "<div>" + lkv + "</div>";
    }
    return lkv;
}


// ==========================================================================================
function kenmerktype_xref (pfield, pprops, pparams)
{
    var lkv = "";

    if (pfield.readonlyfield || pfield.maskeren)
    {
        if (pfield.kwaarde)
            lkv = '<span class="details" onclick=\'openDoc("' + safe.jsstring(pfield.kwaarde) + '")\'>'+safe.htmlattr(pfield.kwaarde)+"</span>";
    }
    else
    {

        lkv = "<input type='text'"
            + (pfield.kregexp ? " regexp='" + pfield.kregexp + "'" : "")
            + " onChange='fvalid=checkKenmerk(this, false,\"" + pfield.kkenmerktype + "\"," + pfield.klengte + "," + pfield.kdec + "," + pfield.knmin + "," + pfield.knmax + ", "
              + (pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null") + ")" + (pfield.usedInExpr ? pparams.onchangeExp : "") + "'"
            + " onBlur='checkKenmerk(this, true,\"" + pfield.kkenmerktype + "\"," + pfield.klengte + "," + pfield.kdec + "," + pfield.knmin + "," + pfield.knmax + ", " + (pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null") + ")'"
            + " id='" + pprops.nameprefix + pfield.idCounter + "val'"
            + " name='" + pprops.nameprefix + pfield.idCounter + "val' ";

        if (pfield.kwaarde) // Tekstveld met 'view' button er achter plakken
        {
            lkv += " class='fld fldflexX "
                 + (pfield.required ? pparams.required_class : "")
                 + (pprops.mobile ? MOBILE_UI_CLASSES.input : "")
                 + "'"
                 + " value='" + safe.htmlattr(pfield.kwaarde) + "'"
                 + (pfield.required ? " required='required' " : "")
                 + "><span class='fileopen details' "
                 + " onClick='openDoc(\"" + safe.jsstring(pfield.kwaarde) + "\");'>" + I("fa-folder-open") + "</span>";
        }
        else if (!pprops.kenmerk_search) // !pfield.kwaarde // Browse button for new er achter plakken
            lkv = "<input class='fld fldflexX "
                + (pfield.required ? pparams.required_class : "")
                + (pprops.mobile ? MOBILE_UI_CLASSES.input : "")
                + "'"
                + (pfield.required ? " required='required' " : "")
                + " onChange='"
                + (pfield.required > 1
                  ? "checkRequiredGroup(\"" + pparams.required_group + "\", " + (pprops.requiredbyemptyval != null? "{emptyvalue: " + pprops.requiredbyemptyval + "}" : "null") + ");"
                  : "")
                + (pfield.usedInExpr ? pparams.onchangeExp : "") + "'"
                + " type='text' "
                + "name='" + pprops.nameprefix + pfield.idCounter + "val' " + "id='" + pprops.nameprefix + pfield.idCounter + "val' "
                + "value='" + safe.htmlattr(pfield.kwaarde) + "'"
                + ">";
        else
            lkv += "class='fld fldflexX'>";
    }
    if (pprops.mobile) {
        lkv = "<div>" + lkv + "</div>";
    }
    return lkv;
}

var behandelblok = 0;
// ==========================================================================================
function kenmerktype_label (pfield, pprops, pparams)
{
  // Een label is geen echt veld, maar een infoblock
  // De 'waarde' van een label is de defaultwaarde van het kenmerk
  var lkv = "";

  if (S("fac_html_strictness") == 0)
      var safe_default = pfield.kdefault
  else
      var safe_default = safe.html(pfield.kdefault);
  lkv = "<span class='flexlabel'>"+(safe_default || "")+"</span>";
  return lkv;
}

// KENMERKTYPE l (kleine letter L) = blokheader (voorheen collapsible label)  ===============
// ==========================================================================================
var canbehandelblok = false;
var blokisafgemeld = false;
function kenmerktype_blocktitel (pfield, pprops, pparams)
{
    // Wordt aangeroepen als er (zeker) een block_start moet komen
    // Het label zelf bestaat uit een icon + label-tekst
    // Het icon staat bij de default waarde van het kenmerk
    // De label-tekst wordt gedefinieerd bij de benaming van het kenmerk
    var lkv = "";
    if (!pprops.kenmerk_search)
    {
        // Schrijf eventueel een block-begin; je mag (nog) niet nesten, dus nieuw block betekent einde vorige block
        // Blocks worden gemarkeerd met een collapsible label, ktype = kleine-letter-l
        // Deze zijn echter optioneel, dus als die er niet zijn, moeten we zelf een (1) blokbegin verzinnen

        if (/*global*/__insideblock > 0) {
            lkv += BLOCK_END_HTML();
            __insideblock = 0; // of -1 om ooit nesting te kunnen ;-)
        }

        // Gelijk aan suppress_label, een blok moet je nog wel printen omdat alle velden er in ook hidden moeten worden
        // Als het behandelbare block behandeld moet worden door de user, dan moet altijd het label getoond worden ook in de show mode.
        var hide_block =
            show_label_expr[pfield.kkey] == 0 ||
            (
                pprops.reado &&
                pfield.kshow_expr &&
                pparams.pkey &&
                !evaluateExpression(
                    pparams.pkey,
                    pfield.kshow_expr,
                    (pparams.module === "MLD" && pparams.flexPath === "MLD/O") ? "OPD" : pparams.module,
                    pprops.kenmerk_module || pprops.niveau || null
                )
            );

        var block_params = { "flexblock": true,
                             "icon": "fa-info-square",
                             "isShowExpr": pfield.isShowExpr,
                             "hidden": hide_block,
                             "collapsible": !pprops.reado && pfield.forceReadonly
                           };
        var show_blok = false;
        if (pprops.treatable && pfield.kkenmerktype == "b")
        { // Het is een Behandelbare bloktitel.
          // Waarde behandelblok.
          // 0) Kenmerk valt niet onder een behandelbaar blok:        Afhankelijk van pvolgnummer.
          // 1) Behandelteam ingevuld en user zit in het team:        Blok altijd tonen.
          // 2) Behandelteam ingevuld en user zit NIET in het team:   Blok niet tonen.
          // 3) Geen behandelteam ingevuld:                           Afhankelijk van rechten.
          behandelblok = pfield.behandelblok;
          blokisafgemeld = pfield.blokafgemeld;
          var id_disc = pprops.nameprefix + pfield.idCounter + "team";
          var id_prs = pprops.nameprefix + pfield.idCounter + "prs";
          block_params.treatable = { "isTreatable": true,
                                     "blokafmelddatum": pfield.blokafmelddatum,
                                     "flexid": pprops.nameprefix + pfield.idCounter,
                                     "kkey": pfield.kkey,
                                     "disc" : { "id": id_disc,
                                                "srtdisc_key": pprops.treatable.srtdisc,
                                                "disc": pprops.treatable.disc,
                                                "disc_key": pfield.blokbehandelteam, // Waarde tertiare Vakgroep/Behandelteam. Voor nieuwe meldingen is deze waarde de default waaarde als er een default waarde is gedefinieerd.
                                                "perslidKey": (pprops.treatable.mld_key > 0? pprops.treatable.melder_key : user_key),
                                                "reado": pprops.reado ||
                                                         pprops.treatable.readodisc ||
                                                         pfield.blokafgemeld
                                              },
                                     "prs": { "id": id_prs,
                                              "disc_key":  pfield.blokbehandelteam, // Waarde tertiare Vakgroep/Behandelteam. Voor nieuwe meldingen is deze waarde de default waaarde als er een default waarde is gedefinieerd.
                                              "disc_id": id_disc,
                                              "prs_key": (pfield.blokbehandelaar? pfield.blokbehandelaar : -1),
                                              "melder_key": pprops.treatable.melder_key || -1,
                                              "prsautlevel": pprops.treatable.prsautlevel,
                                              "reado": pprops.reado ||
                                                       pprops.treatable.readoprs ||
                                                       pfield.blokafgemeld ||
                                                       (pprops.treatable.readodisc && pfield.behandelblok == 3), // Geldt alleen als ik blokbehandelaar ben en dan is pprops.treatable.readodisc true (geen canFlexChange rechten, alleen canChangeKenmerkBlok rechten).
                                              "show_alg": (pprops.treatable.show_alg? 1 : 0),
                                              "loc_key":  (pprops.treatable.loc_key? pprops.treatable.loc_key : -1)
                                            },
                                     "compl": { "value": pfield.blokafgemeld,
                                                "reado": pprops.reado ||
                                                         pprops.treatable.readocompl ||
                                                         (pprops.treatable.readodisc && pfield.behandelblok >= 2) || // Geldt alleen als ik blokbehandelaar ben en dan is pprops.treatable.readodisc true (geen canFlexChange rechten, alleen canChangeKenmerkBlok rechten).
                                                         (pprops.treatable.mld_key < 0)
                                              }
                                   };
          // Toon de behandelbare bloktitel ook als er geen kenmerk is ingevuld indien:
          // 1) Behandelteam ingevuld en user zit in het team.
          // 2) Alle behandelblokken als ik primaire/secundaire behandelaar ben (ik heb canChange rechten).
          show_blok = pfield.behandelblok == 1 || (pprops.treatable.canChange);
        }
        else
        {
          behandelblok = 0;
          blokisafgemeld = false;
          block_params.treatable = { "isTreatable": false };
        }

        var mobile_block_params = { "pId": "flx_" + pprops.nameprefix + pfield.idCounter,
                                    "safe": true,
                                    "title": (pfield.komschrijving ? pfield.komschrijving : (pfield.kkenmerktype == "l"? L("lcl_flexblok") : L("lcl_mld_treatable_flexblok"))),
                                    "collapsed": !pprops.reado && pfield.forceReadonly,
                                    treatable: block_params.treatable
                                  };
        if (pfield.kdefault && pfield.kdefault.match(/^fa-/))
            block_params.icon = pfield.kdefault;
        if (pfield.kregexp && pfield.kregexp.match(/(100|100%)/) != null)
            block_params.wide = true;

        if (pfield.kregexp) {
            var labelWidth = parseInt(pfield.kregexp, 10);
            if (!isNaN(labelWidth) && labelWidth >= 0 && labelWidth < 100) {
                block_params.labelWidth = labelWidth;
            }
        }

        lkv += ( pprops.mobile
               ? BLOCK_START_HTML(mobile_block_params)                                                                                     // pda/iface.inc
               : BLOCK_START_HTML("flx_" + pprops.nameprefix + pfield.idCounter, (pfield.komschrijving ? pfield.komschrijving : (pfield.kkenmerktype == "l"? L("lcl_flexblok") : L("lcl_mld_treatable_flexblok"))), block_params) // shared/iface.inc
               );

        if (pfield.kkey && (pparams.hasAnyFlexExpr || pprops.treatable))
        {
            lkv += "\n<input type='hidden' name='"+pprops.nameprefix + pfield.idCounter+"key'   id='"+pprops.nameprefix + pfield.idCounter+"key'   value='"+pfield.kkey+"'>";
            if (pparams.hasAnyFlexExpr)
              lkv += "\n<input type='hidden' name='"+pprops.nameprefix + pfield.idCounter+"val'   id='"+pprops.nameprefix + pfield.idCounter+"val'   value=''>";
            lkv += "\n<input type='hidden' name='"+pprops.nameprefix + pfield.idCounter+"count' id='"+pprops.nameprefix + pfield.idCounter+"count' value='"+pfield.kkeycount+"'>";
            lkv += "\n<input type='hidden' name='"+pprops.nameprefix + pfield.idCounter+"t'     id='"+pprops.nameprefix + pfield.idCounter+"t'     value='"+pfield.kkenmerktype+"'>";
            if (pfield.isExpression)
            {
                lkv += "\n<input type='hidden' name='"+pprops.nameprefix + pfield.idCounter+"e' id='"+pprops.nameprefix + pfield.idCounter+"e'     value='"+(pfield.isShowExpr ? 2: 1) +"'>";
            }
        }
        /*global*/__insideblock = 1;
        /*global*/allCollapsable = true;            // opnieuw beginnen met zoeken
        /*global*/inMobileBlock = pprops.mobile;    // op dit moment zitten we in een mobile-collapse-content-block
        /*global*/clabelCounter++;
    }
    var km_data = { lkv: lkv, show_blok: show_blok };
    return km_data;
}

// ==========================================================================================
function kenmerktype_spacer (pfield, pprops, pparams)
{
    // Een spacer is geen echt veld maar een horizontale streep om de verticale uitlijning van opvolgende blokken te resetten
    var lkv = "";
    if (pprops.mobile)
        return lkv;
    if (/*global*/__insideblock > 0)
        lkv += BLOCK_END_HTML();

    lkv += SIMPLE_BLOCK_START_HTML({ extraclass: "flexspacer" });
    lkv += SIMPLE_BLOCK_END_HTML();
    __insideblock = 0;

    return lkv;
}

function kenmerktype_attachment_by_srtkenmerk(pfield, pprops, pparams)
{
    var res;
    if (pprops.parent_list)
    {
        var sql_p = "SELECT sk.mld_srtkenmerk_key"
                  + "     , COALESCE(k.mld_kenmerk_omschrijving, sk.mld_srtkenmerk_omschrijving) k_desc"
                  + "  FROM mld_kenmerk k"
                  + "     , mld_srtkenmerk sk"
                  + " WHERE sk.mld_srtkenmerk_key = k.mld_srtkenmerk_key"
                  + "   AND mld_kenmerk_key = " + pfield.kkey;
        var oRs_p = Oracle.Execute(sql_p);
        var srtkm_key = oRs_p("mld_srtkenmerk_key").Value;
        var k_desc = oRs_p("k_desc").Value;
        oRs_p.Close();

        var btn_kenmerken = [];
        for (pp in pprops.parent_list)
        {
            var v_parent = pprops.parent_list[pp];
            var v_kenmerken = v_parent.kenmerken;
            for (var j=0; j<v_kenmerken.length; j++)
            {
                if (v_kenmerken[j].srtkm_key == srtkm_key &&
                    v_kenmerken[j].k_desc == k_desc)
                {
                    btn_kenmerken.push({mld_key: v_parent.key, km_key: v_kenmerken[j].km_key});
                }
            }
        }

        if (btn_kenmerken.length > 0)
        {
            pprops.srtkm_list = btn_kenmerken;
            res = BijlagenButton(pfield, pprops, pparams);
            res.addPreview = res.addPreview && !pprops.nolabel && !pfield.maskeren && pfield.readonlyfield;
            pprops.srtkm_list = [];
        }
    }
    return res;
}

// ==========================================================================================
function kenmerktype_bijlage (pfield, pprops, pparams)
{
    var lkv = "";
    var addPreview = false;
    var kotherkey = null;

    if (pprops.mobile && pfield.kdimensie != 'xxx')
    {
        lkv = mobileBijlagen(pfield, pprops, pparams);
        var km_data = { lkv: lkv };
    }
    else
    {   // Bedoeling is: toon de filenaam op een button
        // In showmode/readonly kun je op een <span> klikken als-ie bestaat, in editmode
        // moet je de change button gebruiken om te wijzigen
        // De echte waarde bewaren we in een hidden veld. Die wordt dan ook gesubmit

        // (mobile) Handtekeningen gaan ook via BijlagenButton met een eigen mechanisme waar afgewisseld wordt tussen;
        // <Leeg> => 'Toevoegen' knop, of gevuld => klikbaar plaatje vd tekening
        var res = BijlagenButton(pfield, pprops, pparams);
        lkv = res.html;
        addPreview = res.addPreview;
        kotherkey = res.kotherkey || -1;

        res = kenmerktype_attachment_by_srtkenmerk(pfield, pprops, pparams);
        if (res)
        {
            if (res.addPreview)
            {
                addPreview = true;
            }
            lkv += res.html;
            if (res.count > 0)
            {
                allCollapsable = false;
            }
        }

        // Add preview link for bijlage
        if (addPreview && !pprops.kenmerk_search)
        {
            var previewUrl = protectQS.create("../Shared/BijlagenPreview.asp"
                                +"?key="+pparams.pkey
                                +"&kkey="+pfield.kkey
                                +"&module="+pparams.module
                                +"&niveau="+pprops.niveau);
%>      <script>
            $(function() {
               $("span.preview#p<%=pfield.idCounter%>").on("click", function() { FcltMgr.openModalDetail("<%=previewUrl%>", L("lcl_flex_preview")) });
            });
        </script>
<%      }
        var km_data = { lkv: lkv, addPreview: addPreview, kotherkey: kotherkey };
    }
    return km_data;
}

// ==========================================================================================
function kenmerktype_folder (pfield, pprops, pparams)
{
    var lkv = "";
    var addPreview = false;
    var previewLink = { keys: [],
                        kkeys: [],
                        module: pparams.module,
                        niveau: pprops.niveau };

    if (pprops.mobile && pfield.kdimensie != 'xxx')
    {
        lkv = mobileBijlagen(pfield, pprops, pparams);
        var km_data = { lkv: lkv };
        return km_data;
    }

    var res = BijlagenButton(pfield, pprops, pparams);
    addPreview = res.addPreview && !pprops.nolabel && !pfield.maskeren && pfield.readonlyfield;
    if (addPreview)
    {
        previewLink.keys.push(pparams.pkey);
        previewLink.kkeys.push(pfield.kkey);
    }

    lkv = res.html;
    if (res.count > 0)
        allCollapsable = false;

    // If the 'child' was not preview-able, then start extending the Id count with -1 instead of 0
    res = kenmerktype_attachment_by_srtkenmerk(pfield, pprops, pparams);
    if (res)
    {
        if (res.addPreview)
        {
            addPreview = true;
            for (var i=0; i<pprops.parent_list.length; i++)
            {
                for (var j=0; j<pprops.parent_list[i].kenmerken.length; j++)
                {
                    previewLink.keys.push(pprops.parent_list[i].key);
                    previewLink.kkeys.push(pprops.parent_list[i].kenmerken[j].km_key);
                }
            }
        }

        lkv += res.html;
        if (res.count > 0)
            allCollapsable = false;
    }

    if (pfield.Classname && lkv=="")
    {
        lkv = "<div class='readonly'></div>";
    }

    // Add preview links for folders
    if (addPreview && previewLink.keys.length && !pprops.kenmerk_search)
    {
        var previewUrl = protectQS.create("../Shared/BijlagenPreview.asp"
                            +"?key="+previewLink.keys.join(",")
                            +"&kkey="+previewLink.kkeys.join(",")
                            +"&module="+previewLink.module
                            +"&niveau="+previewLink.niveau
                                         );
        %><script>
        $(function()
        {
           $("span.preview#p<%=pfield.idCounter%>").on("click", function() { FcltMgr.openModalDetail("<%=previewUrl%>", L("lcl_flex_preview")) });
        });
      </script><%
    }

    var km_data = { lkv: lkv, addPreview: addPreview };
    return km_data;
}

// Mobiel tonen we de bijlagen heel anders
function mobileBijlagen (tpfield, tpprops, tpparams)
{
    var tkv = "";

    var default_afmeting = "";
    var values = "";
    var default_afmeting_width = -1;
    var default_afmeting_height = -1;
    if (tpfield.kregexp)
    {
      default_afmeting = tpfield.kregexp.substr(1);
      values = default_afmeting.split("x");
      default_afmeting_width = parseInt(values[0]);
      default_afmeting_height = parseInt(values[1]);
    }

    var flex_bijlagen = flexProps(tpparams.module, tpparams.pkey, String(tpfield.kkey), tpprops.niveau);
    var lijst = flex_bijlagen.files;
    var filehtmlarr = [];
    for (var i = 0; i < lijst.length; i++)
    {
        var previewClass = '';
        if (new RegExp(S("flexPreviewExt"),"i").test(lijst[i].name)) {
            previewClass = 'previewext';
        }
        var idval = "kid{0}_{1}".format(tpfield.idCounter, i);
        var href = protectQS.create(OpenFlexFile(tpparams.module, lijst[i].key, { name: lijst[i].name }));
        var delete_function = "DeleteFile('" + safe.html(lijst[i].name) + "', '" + safe.jsstring(protectQS.create("Bijlagenform_delete.asp?DoDelete=" + Server.URLEncode(lijst[i].name) + "&key=" + lijst[i].refkey + "&module=" + tpparams.module + "&niveau=" + lijst[i].niveau + "&kenmerk_key=" + lijst[i].kmkey + "&multi=1")) + "', $(this).closest('div'))";
        var html = '<div class="btn-wrapper">'
                 + '  <a id="'+idval+'" class="' + previewClass + MOBILE_UI_CLASSES.button + '" role="button"'
                 + '     href=\"' + safe.htmlattr(href) + '" '  + (device.isapp()? '' : 'target="_blank"') + '>'
                 + safe.html(lijst[i].name)
                 + '  </a>'
                 + (tpfield.readonlyfield || ("canDeleteAttachment" in tpprops && !tpprops.canDeleteAttachment) ? '' : '<a id="'+idval+'" href="#" onclick="' + delete_function + '" class="' + MOBILE_UI_CLASSES.button + '" title="' + L("lcl_filedelete") + '" role="button">' + I("fa-trash-alt") + '</a>')
                 // kenmerktype = 'M' heeft dit veld niet nodig, als deze er wel staat wordt de bestandsnaam via mobile in de kenmerktabel opgeslagen, en dat moet niet (#71024)
                 + (tpfield.kkenmerktype == 'M' ? '' : '<input type="hidden" class="icon icon-attachment filename readonly" name="'+tpprops.nameprefix + tpfield.idCounter + "val"+'" value="'+safe.html(lijst[i].name)+'">')
                 + '</div>';



        filehtmlarr.push(html);
    }

    if (!tpfield.readonlyfield) {
        var btn_icon = "fa-file"; // [F]ile
        if (tpfield.kkenmerktype == "M") { // [M]ap
            btn_icon = "fa-folder-open";
        } else if (tpfield.kkenmerktype == "E") { // [E]ncrypted
            btn_icon = "fa-file-lock";
        }

        tkv += '<div class="attachment-wrapper">';
        tkv += '  <span class="attachments_form">';
        tkv += '    <span class="attachments_fields">';
        tkv += filehtmlarr.join("");
        tkv +=     '</span>'; // Geen spaties in deze <span> (!)
        tkv += '    <span class="' + (tpfield.kkenmerktype == 'M' ? '' : 'single ') + 'add_attachment">';
        tkv += BUTTON_HTML(L("lcl_select_file_mobile") + (tpfield.kdimensie ? " (" + safe.html(tpfield.kdimensie) + ")" : ""), {
            "id": tpprops.nameprefix + tpfield.idCounter + "val",
            "cls": (tpfield.isSqlExpr ? " flexsql" : "") + (tpfield.isExpression ? " expression " + (tpfield.isShowExpr ? "flexshow" : "flexexpr") : "") + (tpfield.required ? tpparams.required_class : "")
                    + MOBILE_UI_CLASSES.button, /* Dynamisch herladen voegt deze classes niet in */
            "icon": btn_icon,
            "click": "FcltMgr.stopPropagation(); $('#attachments"+tpfield.kkey+"').trigger('click')",
            "attributes": {
                "isBijlage": 1,
                "nBijlagen": lijst.length
            }
        });
        tkv += '      <input type="file"';
        tkv += '             name="attachments'+tpfield.kkey+'"';
        tkv += '             id="attachments'+tpfield.kkey+'"';
        tkv += '             class="file_selector filedrop"';
        tkv += (tpfield.kkenmerktype == 'M' ? ' multiple="multiple"' : '');
        tkv += '             onchange="addInputFiles(this);"';
        // tkv += '           data-max-file-size="5242880"';
        // tkv += '           data-max-file-size-message="This file cannot be uploaded because it exceeds the maximum allowed file size (5 MB)"';
        tkv += '             data-max-default-width="' + default_afmeting_width + '"'
        tkv += '             data-max-default-height="' + default_afmeting_height + '"'
        tkv += '             data-max-concurrent-uploads="2"';
        tkv += '             data-upload-path="{0}/api2/{1}/{2}/attachments/{3}/"'.format(rooturl, tpparams.api2name, tpparams.pkey<0?0:tpparams.pkey, tpfield.kkey);
        tkv += '             data-param="attachments"';
        tkv += '             data-description="true"';
        // tkv += '           data-description-placeholder="Optional description"';
        // tkv += '           (Maximum size: 5 MB)';
        tkv += '      >';
        tkv += '    </span>';
        tkv += '  </span>';
        tkv += '</div>';
    } else {
        tkv += filehtmlarr.join("");
    }

    return tkv;
}



var minMax           = [];          // te gebruiken uren voor timepicker vastleggen.

// ================================================================================================
// 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 (oRs)          - true betekent onzichtbaar
//                 fnIsReadonly: optioneel function (oRS)            - true betekent readonly
//                 fnIsHidden: optioneel function (rolcode, default) - true betekent hidden (onzichtbaar maar wel aanwezig i.v.m. opslaan)
//                 fnClassname: optioneel function()                 - geeft een class of lege string terug
//                 parentKey: optioneel om flexkenmerken onder op te slaan
//                 kenmerk_module: optioneel bij ALG: "L", "G", "R" etc.
//                 multiMode
//                 multiEdit: zet een checkbox voor het veld. Alleen (nog) voor MLD.
//                 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
//                 wfbuilder: toon ook flex-colname :f123 (voor workflow expression builder)
//                 showConfidential: als true mogen vertrouwelijke waarden getoond worden, anders gemaskeerd
//                 sortFilename: optioneel - true betekent bestandenlijst wordt op naam gesorteerd, anders op datum.
//               }
// Qua confidential-maskeren zijn er 2 aspecten:
// 1. zorgen dat de echt waarden niet getoond worden, dat kan lomp als allereerste actie
//    maar ik verwacht dat men ooit bv wel de bijlagenaam wil tonen maar dan onklikbaar
// 2. zorgen dat de gemaskeerde waarden niet gesubmit worden, dan dus geen submitvelden opnemen
//
// ================================================================================================
function listKenmerk(sql, module, pkey, props)
{
    //props.kenmerk_search;                                         // zoekmode of niet
    //props.multiMode;                                              // TODO: support this!?!
    //props.hideVervallen;                                          // voor list/suggest
    props.prs_key                   = props.prs_key || props.reqId; // voor persoonsafhankelijke kenmerkformule; Oude obsolete reqId nog even ondersteunen
    //props.cont_key;                                               // voor contactpersoonsafhankelijke kenmerkfomule
    props.nameprefix                = props.nameprefix || "k";      //
    props.extraserie                = props.extraserie || false;    //
    props.niveau                    = props.niveau || "";           //
    //props.opdr_type;                                              // voor bijlagen
    //props.srtdeelKey;                                             // alternatieve key voor sleutelmodule
    //props.requiredbyfield;                                        // ??
    //props.requiredbyemptyval;                                     // ??
    props.serie = props.serie || 0;                                 //
    //props.parent_list;                                            // Maak voor deze keys ook bijlagen knop
    //props.mobile;                                                 // mobiele GUI

    //props.kenmerk_extraTD;                                        // mwoah
    //props.notr;                                                   // Geen tr's toevoegen.
    //props.nolabel;                                                // Geen labels toevoegen.
    props.flexstart                 = props.flexstart || 1;         // De kenmerken vanaf de flexstart-ste element tonen (flexstart >= 1 indien meegegeven).
    props.flexend                   = props.flexend || -1;          // De kenmerken tot het flexend-ste element tonen {flexend >= 1 indien meegegeven}.
    props.hasfilter                 = props.hasfilter || false;     // De kenmerken kunnen op naam gefilterd worden.
    props.tmpfolder                 = props.tmpfolder || "";        //
    //props.copy                                                    // Bij het kopieren of vervolgen van een opdracht.
    //props.getTmpfolder                                            // indien true, dan levert deze functie de (onderweg bepaalde) tmpfolder op(??)
    //props.showConfidential;                                       //
    //props.forceRequired;                                          // In case only status dependent characteristics are shown (close confirm/...etc)
    props.force_default_when_null   = props.force_default_when_null || false;  //
    props.inline                    = props.inline || false;        // tijdelijk misschien, om blockheader te voorkomen

    // Ik moet de API2 name weten voor de bestandskenmerken-mobiel. Hier nog iets beters voor verzinnen...?
    if (props.mobile)
    {
        var api2name = { "ALG": { "L": "locations",
                                  "G": "buildings",
                                  "R": "rooms"
                                },
                         "BES": "INTERNAL_ERROR_INVALID_API2NAME", // wel via mobile, maar readonly dus geen api2name nodig
                         "BEZ": "visitors",
                         "CNT": "INTERNAL_ERROR_INVALID_API2NAME", // wel via mobile, maar readonly dus geen api2name nodig
                         "FAQ": "INTERNAL_ERROR_INVALID_API2NAME", // wel via mobile, maar readonly dus geen api2name nodig
                         "FIN": "INTERNAL_ERROR_INVALID_API2NAME", // wel via mobile, maar readonly dus geen api2name nodig
                         "MLD": { "M": "issues",
                                  "O": "orders2"
                                },
                         "PRS": { "P": "persons",
                                  "A": "departments",
                                  "B": "companies"
                                },
                         "INS": { "I": "inspections",
                                  "D": "objects"
                                  // "S": "sleutels"?
                                },
                         "RES": "reservations"
                       }[module] // niveau
        if (api2name && typeof api2name == 'object')
            api2name = api2name[props.kenmerk_module || props.niveau || "D"]; // kenmerk_module voor ALG, niveau voor INS
        if (!api2name)
        {
            __DoLog(props);
            __DoLog("What should api2name be for {0} and {1}?".format(module, props.kenmerk_module));
            INTERNAL_ERROR_UNDEFINED_API2NAME;
        }
    }

    var vars = {};

    var colCount         = 0;          // Even/oneven zodat we weten of we links of rechts zitten
    var forceNewLine     = false;      // Bij labels en grote text velden
    var idCounter        = 1;          // We maken alleen name/id-s voor de velden die ook echt bewerkt kunnen worden
    var anyDate          = false;
    var anyTime          = false;
    var anyTextarea      = false;
    var anySelect        = false;
    var anyTreatable     = false;
    var anyCollapsible   = false;
    var anyMobileBijlagen= false;
    var anyHint          = false;
    var blockId          = 0;
    var blockShow        = [];
    var kenmerkidref     = new Array(); // Bijhouden welk kenmerk welke id referentie mee heeft gekregen
    var dpYearRanges     = [];          // te gebruiken jaren voor datepicker vastleggen.
    var attach_km_list   = [];          // Bijhouden welke bijlagen mappen al zijn gelezen (voor toevoegen van opdracht bij melding)
    var kotherkey        = null;        // Wordt voor type 'E' en 'F' gevuld met de kenmerkkey waar deze bijlage vandaan komt

    var allCollapsable   = false;       // true bij eerste 'l'-label
    var inMobileBlock    = false;       // true van begin mobile-collapse-content tot eind mobile-collapse-content

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

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

    // Zitten er expressie kenmerken tussen?
    var hasAnyFlexExpr = false;
    var hasAnyShowExpr = false;
    var kenmerkUsedInExpr = []; // Neem hier de kenmerken op die een effect kunnen hebben op de waarde/zichtbaarheid van andere kenmerken
    var matchedKeys;
    var flexpregex = /:flex(\d+)/gi;

    var oRs = Oracle.Execute(sql);  // De hoofdquery, eerste keer
    var hasAnyKenmerk = !oRs.EOF; // dan hoeft de twee keer niet
    // Komt er uberhaupt iets moeilijks voor? Meestal niet namelijk
    while (!oRs.eof) {
        var kdefault  = oRs("kenmerk_default").Value;
        var kshowexpr = oRs("kenmerk_show_expr").Value;
        if ((kdefault && (kdefault.match('##EXPR##') != null)) ||
            (kshowexpr && kshowexpr.match('##SHOW##') != null))
        {
            hasAnyFlexExpr = true; // Omvat zowel ##EXPR## als ##SHOW##
            if (kdefault && (kdefault.match('##EXPR##') != null)) {
                while (matchedKeys = flexpregex.exec(kdefault)) {
                    if (!inArray(matchedKeys[1], kenmerkUsedInExpr)) { // Alleen unieke waardes pushen
                        kenmerkUsedInExpr.push(matchedKeys[1]);
                    }
                }
            }
            if (kshowexpr && kshowexpr.match('##SHOW##') != null) {
                hasAnyShowExpr = true;
                while (matchedKeys = flexpregex.exec(kshowexpr)) {
                    if (!inArray(matchedKeys[1], kenmerkUsedInExpr)) { // Alleen unieke waardes pushen
                        kenmerkUsedInExpr.push(matchedKeys[1]);
                    }
                }
            }
        }
        oRs.MoveNext();
    }
    oRs.Close();
    matchedKeys = null;
    flexpregex = null;
    kdefault  = null;
    kshowexpr = null;

    // Labels with ##show## expression in SHOW mode must be suppressed when the fac_kenmwaarden_exprwaarde = 0
    // because labels always have a value they will always show, even when they have a ##show## expression
    // PF: ik begrijp niet zomaar waarom je dat niet bij het label regelt? Omdat je dan per label moet querien?
    if (pkey > -1 && (props.reado || false) == true)
    {
        var label_sql = "SELECT fac_kenmwaarden_kenmerk_key, fac_kenmwaarden_exprwaarde "
                      + "  FROM fac_kenmwaarden "
                      + " WHERE fac_kenmwaarden_module = '" + module + "'"
                      + "   AND fac_kenmwaarden_refkey = " + pkey
                      + "   AND fac_kenmwaarden_kenmerktype IN ('L','l')"
                      + "   AND fac_kenmwaarden_isexpr = '1'";
        var label_oRs = Oracle.Execute(label_sql);
        while (!label_oRs.eof)
        {
            show_label_expr[label_oRs("fac_kenmwaarden_kenmerk_key").Value] = label_oRs("fac_kenmwaarden_exprwaarde").Value;
            label_oRs.MoveNext();
        }
        label_oRs.Close();
    }

    if (hasAnyKenmerk)
    {
        __Log("Opnieuw dezelfde query is niet supperefficient maar een MoveFirst is problematisch (FSN#39008)");
        var oRs = Oracle.Execute(sql);
    }

    if ((pkey < 0 || props.copy) && props.tmpfolder == "")
        props.tmpfolder = shared.random(32); // voor als nog geen key en wel bijlagen

    var prssql = false;

    // ============================================================================================
    // Doorloop alle flexkenmerken

    if (props.kenmerk_search)
        kenmerk_write("<table class='fcltblocktab'>");
    else if (!props.reado) { // Leave a marker for dynamic (re)loading of kenmerken
        kenmerk_write("<flex-marker></flex-marker>"); // Custom elementje
    }

    var hasfilter_cnt = 0;
    for (i = 1; hasAnyKenmerk && !oRs.eof; i++)
    {
        var thisfield = {};
        thisfield.idCounter = idCounter;

        kotherkey = null;
        thisfield.separator  = ''; // Standaard
        thisfield.kkenmerktype  = oRs("kenmerk_kenmerktype").Value;
        /*  Kenmerktype vertaling;
            C = [C]haracter (string)
            N = [N]umeric (number)
            D = [D]ate
            T = [T]ime
            R = [R]eferentie (listbox)
            S = Referentie [S]uggest
            X = Filename
            F = [F]ile
            E = [E]ncrypted file
            M = [M]ap met bestanden
            L = [L]abel
            l = B[l]oktitel
            b = Autoriseerbaar [b]loktitel
            Q = Nieuwe sectie
            V = [V]inkje
            ------------------------------
            Removed: B = [B]erekend (##EXPR## is hiervoor bedacht)
            ------------------------------
            [BES] module heeft dezelfde kenmerk*soort*-types, maar ook *kenmerk*-types;
            B = [B]estelling-kenmerken
            I = Bestel-[I]tem-kenmerken
        */
        thisfield.kdefault   = oRs("kenmerk_default").Value;
        thisfield.kshow_expr = oRs("kenmerk_show_expr").Value;

        thisfield.isExpression = false;
        thisfield.isReadonly   = false;
        thisfield.isShowExpr   = false;
        thisfield.isSqlExpr   = false;
        thisfield.Classname = (props.fnClassname ? props.fnClassname(oRs) : "");
        //ExpressionClass= (params.isShowExpr ? "flexshow" : "flexexpr")
        addPreview = false;

        if (!props.kenmerk_search && !props.multiMode && !props.multiEdit)
        {
            // In multiMode/multiEdit no default values
            thisfield.kwaarde = oRs("kenmerk_waarde").Value;
            try // Het kenmerk wissen staat niet in alle kenmerk-queries!
            {   thisfield.wissen = oRs("kenmerk_wissen").Value == 1; }
            catch(e)
            {   thisfield.wissen = false; }

            thisfield.isExpression = (thisfield.kdefault && thisfield.kdefault.match('##EXPR##') != null);
            if (thisfield.isExpression)
                thisfield.isReadonly = true;
            thisfield.isSqlExpr = (thisfield.kdefault && thisfield.kdefault.match('##SQL##') != null);
            thisfield.isShowExpr = (thisfield.kshow_expr && thisfield.kshow_expr.match('##SHOW##') != null);
            if (thisfield.isShowExpr)
                thisfield.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 (thisfield.kwaarde == null && props.force_default_when_null && thisfield.kdefault)
                thisfield.kwaarde = thisfield.kdefault;
            if (thisfield.kwaarde != null)
            {
                if (thisfield.kwaarde == thisfield.kdefault && thisfield.kwaarde.match('##SQL##') != null)
                {
                    prssql = true;
                    thisfield.kwaarde = thisfield.kwaarde.substr(thisfield.kwaarde.indexOf('##SQL##') + 7);
                    var sql2 = thisfield.kwaarde.replace("##PRS_PERSLID_KEY##", props.prs_key).replace("##PRS_CONTACTPERSOON_KEY##", props.cont_key);
                    var oRsPerslid = Oracle.Execute("SELECT ({0}) FROM DUAL".format(sql2)); // forceer 'SELECT' statements
                    if (oRsPerslid(0).Value != null)
                        thisfield.kwaarde = String(oRsPerslid(0).Value);
                    else
                        thisfield.kwaarde = '';
                    oRsPerslid.Close();
                }
                else if (thisfield.kdefault && thisfield.kwaarde == thisfield.kdefault && thisfield.kdefault.match('##EXPR##') != null)
                {
                    thisfield.kwaarde = "";
                }
            }
        }

        // 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")
            {
                thisfield.kwaarde = xx;
                if (oRs("kenmerk_kenmerktype").Value == "C" || oRs("kenmerk_kenmerktype").Value == "N" || oRs("kenmerk_kenmerktype").Value == "V")
                    thisfield.kwaarde = thisfield.kwaarde.substr(0, oRs("kenmerk_kenmerktype").Value == "V"? 1 : oRs("kenmerk_lengte").Value);  // Kap de waarde op de maximale lengte af
            }
        }

        if (oRs("kenmerk_key_count").Value > 1) {
            thisfield.kkey        = oRs("srtkenmerk_key").Value;
        } else {
            thisfield.kkey        = oRs("kenmerk_key").Value;
        }
        thisfield.kkeycount     = oRs("kenmerk_key_count").Value;
        thisfield.komschrijving = (oRs("kenmerk_omschrijving").Value ? oRs("kenmerk_omschrijving").Value : "");
        thisfield.klengte       = oRs("kenmerk_lengte").Value;
        if (!thisfield.klengte)
            thisfield.klengte = 4000;
        thisfield.kdec          = oRs("kenmerk_dec").Value;
        thisfield.knmin         = oRs("kenmerk_nmin").Value;
        thisfield.knmax         = oRs("kenmerk_nmax").Value;
        thisfield.ksysteem      = oRs("kenmerk_systeem").Value;
        thisfield.kvolgnr       = "" + oRs("kenmerk_volgnr").Value;
        thisfield.krolcode      = oRs("kenmerk_rolcode").Value;     // bitwise: zichtbaar voor welke rollen?
        thisfield.kdimensie     = oRs("kenmerk_dimensie").Value;    // beperkt bij uploads de extensie
        thisfield.khint         = oRs("kenmerk_hint").Value;
        thisfield.kregexp       = oRs("kenmerk_regexp").Value;
        thisfield.kdomein_key   = oRs("kenmerkdomein_key").Value;
        thisfield.required      = oRs("kenmerk_verplicht").Value;   // dit is een integer, geen boolean
        thisfield.reqbystatus   = module == "MLD" && !props.kenmerk_search ? oRs("kenmerk_verplicht_status").Value : -1;
        thisfield.reqgroup      = oRs("kenmerk_groep").Value;
        thisfield.otherPath     = oRs("otherpath").Value;           // otherKenmerkPath, alleen voor meldingen en facturen (tbv kopieren)
        thisfield.functie_key   = oRs("fac_functie_key").Value;     // Extra Autorisatie rechten.
        thisfield.usedInExpr    = inArray(thisfield.kkey, kenmerkUsedInExpr);
        if (module == "MLD" && props.treatable)
        {
          thisfield.blokbehandelteam            = oRs("blokbehandelteam").Value || -1;  // Blokbehandelteam.
          thisfield.blokbehandelaar             = oRs("blokbehandelaar").Value || -1;  // Blokbehandelaar.
          thisfield.blokafgemeld                = oRs("blokafmelddatum").Value != null? true : false;  // Blok is afgemeld als blokafmelddatum is gevuld.
          thisfield.blokafmelddatum             = oRs("blokafmelddatum").Value;
          // Waarde behandelblok.
          // 0) Kenmerk valt NIET onder een behandelbaar blok:
          // 1) Behandelteam ingevuld en user zit in het team:
          // 2) Behandelteam ingevuld en user zit NIET in het team of geen behandelteam ingevuld:
          thisfield.behandelblok                = oRs("behandelblok").Value;
        }

        if (thisfield.functie_key)
          thisfield.additAuthrights = user.checkAutorisation(thisfield.functie_key, true); // Additional Authorization rights.

        // afgeleide veldeigenschappen
        thisfield.maskeren      = !props.showConfidential && (thisfield.ksysteem&4);

        thisfield.required = props.forceRequired || thisfield.Classname ? 1 : thisfield.required;
        if ((props.sqldefaultnotrequired && thisfield.kdefault && thisfield.kdefault.match("##SQL##")))
            thisfield.required = 0;  // false

        // forceReadonly kan gezet zijn: dan is het veld *altijd* readonly en kan alleen ingevuld zijn
        // doordat een extern proces (importjob?) dat doet of het wordt gevuld door de default waarde.
        // Als er extra autorisatie is gezet en je hebt hiervan alleen lees rechten, dan ook readonly forceren.
        thisfield.forceReadonly = oRs("kenmerk_toonbaar").Value == 1 ||
                                  (thisfield.additAuthrights && thisfield.additAuthrights.ALGreadlevel < 9 && thisfield.additAuthrights.PRSreadlevel < 9 &&
                                                                thisfield.additAuthrights.ALGwritelevel > 5 && thisfield.additAuthrights.PRSwritelevel > 5);

        // Velden die de gebruiker niet mag zien zijn we héél snel klaar mee
        // Ook velden waar je niet op kunt zoeken (bv vertrouwelijke) 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 (  (!props.kenmerk_search && props.fnIsInvisible && props.fnIsInvisible(oRs))
            || (props.kenmerk_search && (thisfield.kkenmerktype == "L" || thisfield.kkenmerktype == "l" || thisfield.kkenmerktype == "b" || thisfield.kkenmerktype == "Q" || thisfield.kkenmerktype == "M" || thisfield.kkenmerktype == "F" || thisfield.kkenmerktype == "E") || thisfield.kvolgnr < 0)
            || (!props.kenmerk_search && thisfield.kkenmerktype == "L" && thisfield.forceReadonly && props.fnIsReadonly && props.fnIsReadonly(oRs))
            || (props.kenmerk_search && (thisfield.ksysteem&4)
            || (thisfield.functie_key && !thisfield.additAuthrights)) // Als er geen rechten zijn op de extra autorisatie, dan het kenmerk niet tonen.
           )
        {
            oRs.MoveNext();
            continue; // Onzichtbaar
        }

        //kenmerk_write( "<!--YY:"+thisfield.idCounter+":"+thisfield.komschrijving+"-->");

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

        if (thisfield.readonlyfield || props.kenmerk_search)
            thisfield.required = 0; //false

        var required_class = "";
        var required_group = "";
        if (thisfield.required != 0)
        {
            required_class = (thisfield.Classname? thisfield.Classname : " required");
            // Indien required afhankelijk is van een gewoon veld dan requiredgroup gebruiken met groep >= 10
            if (props.requiredbyfield && thisfield.required == 1)
            {
                thisfield.required = 10;
            }

            if (thisfield.required > 1)
            {
                if (thisfield.required == 2)
                    required_group = thisfield.reqgroup + "S" + parseInt(props.serie, 10);
                else
                    required_group = thisfield.required + "S" + parseInt(props.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;
%>          <span id="requiredgeg_<%=props.nameprefix + thisfield.idCounter%>" style="display:none">
              <script type="text/javascript">
                $(function()
                {
<%                if (thisfield.required > 9)
                  { // Gewone veld ook dezelfde requiredgroup class geven
%>
                    $("#<%=props.requiredbyfield%>").addClass("<%=required_class%>");
<%                } %>
                    checkRequiredGroup("<%=required_group%>", <%=(props.requiredbyemptyval != null? "{emptyvalue: " + props.requiredbyemptyval + "}" : "null")%>);
                    $("#requiredgeg_<%=props.nameprefix + thisfield.idCounter%>").remove(); // Na het uitvoeren kan de ready functie weer weg zodat deze niet
                });
              </script>
            </span>
<%              }
            }
        }

        // 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 (thisfield.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 ut2.fac_usrtab_verwijder IS NULL"
                 + "   AND kd1.fac_kenmerkdomein_key = " + thisfield.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 (props.kenmerk_search) thisfield.readonlyfield = false;
            }
            oRs2.close();
        }

        // We kijken wanneer we dit veld direct sowieso kunnen skippen
        // In het geval van readonly laten we velden zonder waarde helemaal niet zien,
        // met uitzondering van:
        // 1) Labels en
        // 2) Kenmerken die verplicht zijn bij het afmelden (MNNL#64003)
        // 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)
        // Als geavanceerd dan maakt het niet uit of het readonly is. Dan moet je het kenmerk gewoon tonen.
        // @@TODO: dit ziet er niet helemaal scherp uit met die kenmerk_search, DeMorgan?
        if (thisfield.readonlyfield
            && ((!thisfield.kwaarde || thisfield.kwaarde=="") && thisfield.Classname=="")
            && !isParent
            && thisfield.kkenmerktype != "L"
            && thisfield.kkenmerktype != "l"
            && thisfield.kkenmerktype != "b"
            && thisfield.kkenmerktype != "M"
            && (!props.kenmerk_search &&
                (thisfield.kkenmerktype == "C" ||
                 thisfield.kkenmerktype == "N" ||
                 thisfield.kkenmerktype == "V" ||
                 thisfield.kkenmerktype == "D" ||
                 thisfield.kkenmerktype == "T" ||
                 thisfield.kkenmerktype == "R" ||
                 thisfield.kkenmerktype == "r" ||
                 thisfield.kkenmerktype == "S"))
           )
        {
            oRs.MoveNext();
            continue;
        }


        // 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[props.nameprefix + thisfield.kkey] = new idref(props.nameprefix + thisfield.idCounter, thisfield.kwaarde);
        var parentkenmerkid = "";
        var parentkenmerk_key = -1;
        var parentkenmerkwaarde;

        // Als er bij kenmerk_search meerdere afhankelijke (=children) kenmerken zijn, dan hoeft deze check ook niet
        if (!thisfield.readonlyfield && !(thisfield.kkeycount > 1 && props.kenmerk_search) && (thisfield.kkenmerktype == "S" || thisfield.kkenmerktype == "r" || thisfield.kkenmerktype == "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"
                     + "     , tfin.kenmerk_key_count"
                     + "     , tfin.kenmerk_volgnr"
                     + "  FROM fac_usrtab ut1"
                     + "     , fac_usrtab ut2"
                     + "     , fac_kenmerkdomein kd1"
                     + "     , fac_kenmerkdomein kd2"
                     + "     , (" + sql + ") tfin"
                     + " WHERE kd1.fac_kenmerkdomein_key = " + thisfield.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(thisfield.kkenmerktype)
                     + " ORDER BY tfin.kenmerk_volgnr"
          var oRs2 = Oracle.Execute(sqlDep);
          // Indien kenmerk_key_count > 1, dan zijn er meerdere (gegroepeerde) parents gevonden, dan geen dependency check bij kenmerk_search
          if (!oRs2.eof && !(oRs2("kenmerk_key_count").Value > 1 && props.kenmerk_search))
          { // Er is een afhankelijkheid met een ander kenmerk
            var volgnummer = oRs2("kenmerk_volgnr").Value;
            var temp_parentkenmerk_key = oRs2("kenmerk_key").Value;
            oRs2.MoveNext();
            if (oRs2.eof) // Er is maar 1 parent gevonden -> doe de dependency check
            {
              if (thisfield.kvolgnr < volgnummer)
              {
                __DoLog("INTERNAL_ERROR: kenmerk {0}, kenmerkkey {1}".format(thisfield.komschrijving, thisfield.kkey));
                _AiAi("INTERNAL_ERROR: Parent property not found in front of dependent property with key " + thisfield.kkey);
              }
              else
              {
                parentkenmerk_key = temp_parentkenmerk_key;
                parentkenmerkid = kenmerkidref[props.nameprefix + parentkenmerk_key].idname;
                parentkenmerkwaarde = kenmerkidref[props.nameprefix + parentkenmerk_key].waarde;
              }
            }
            else
            {
              __DoLog("kenmerk {0} (key {1}) is afhankelijk van TWEE parents (key {2} en {3}).\nDat is een inrichtingsfout. Afhankelijkheid wordt genegeerd"
                              .format(thisfield.komschrijving, thisfield.kkey, temp_parentkenmerk_key, oRs2("kenmerk_key").Value), "#FFFF00");
            }
          }
          oRs2.close();
        }

        // Als geavanceerd dan maakt het niet uit of het readonly is. Dan moet je het kenmerk gewoon tonen.
        if (props.kenmerk_search && thisfield.readonlyfield)
          thisfield.readonlyfield = false;


        colCount++; // Nu pas ophogen.

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

        if (thisfield.kkenmerktype == "L" || thisfield.kkenmerktype == "l" || thisfield.kkenmerktype == "b")
        {
          forceNewLine = true;
          colCount = 2; // Forceer "even"
          if (inMobileBlock)
            kenmerk_write("</div>");
        }
        else
          forceNewLine = false;

        if (props.kenmerk_search || thisfield.kwaarde != null && thisfield.kwaarde != "")
          allCollapsable = false;



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

        // Een editable flexkenmerk wordt altijd weergegeven volgens het volgende format:
        // <tr><td class="label"><label>LABEL</label></td><td><input type="text" value="XX"></td></tr>
        // Een readonly flexkenmerk wordt altijd weergegeven volgens het volgende format:
        // <tr><td class="label"><label>LABEL</label></td><td><span>XX</span></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

        // Als er expresssies in het spel zijn, dan berekenen we bij het verlaten met deze functie
        // die we dan voor de clientside toevoegen aan de betrokken velden
        // PAS OP: deze codestrings bevatten DUBBELE quotes, en moeten bij gebruik dus in ENKELE quotes gezet worden
        // (nog uitzoeken of de if fvalid versie niet gewoon altijd kan)
        if (hasAnyFlexExpr)
        {
          onchangeExp = ";calculateFlexExpressions(this);";
          onchangeExpValid = ";if (fvalid) calculateFlexExpressions(this);";
        }

        // Bijhouden welk kenmerk welke id referentie mee heeft gekregen
        function idref(idname, waarde)
        {
          this.idname = idname;
          this.waarde = waarde;
        }


        // we hebben nu 3 groepen:
        // thisfield met veldeigenschappen
        // props met flexblokeigenschappen
        // en nu ook params om afgeleid bepaalde zaken mee te geven
        params= {
              pkey:                 pkey,
              module:               module,
              hasAnyFlexExpr:       hasAnyFlexExpr,
              parentkenmerkid:      parentkenmerkid,
              parentkenmerkwaarde:  parentkenmerkwaarde,
              required_class:       required_class,
              required_group:       required_group,
              onchangeExp:          onchangeExp,
              onchangeExpValid:     onchangeExpValid,
              api2name:             api2name
        };

        // Auto-Add generieke BLOCK_START als het eerste veld geen bloktitel is
        if (__insideblock == 0                  // kenmerk buiten BLOCK (mag niet, m.u.v. Nieuwe sectie "Q")
            && !props.kenmerk_search            // Tenzij in kenmerkfilter
            && thisfield.kkenmerktype != "l"    // geen collapsible block (die komt automatisch zelf met een blocktitel)
            && thisfield.kkenmerktype != "b"    // geen collapsible block (die komt automatisch zelf met een behandelbare blocktitel)
            && thisfield.kkenmerktype != "Q"    // geen blok bij een nieuwe sectie (die is op block-niveau)
            && !props.inline                    // inline moet geen blocktitel
            && !props.noblock)
        {
            // Een blok verzinnen. De field-eigenschappen gelden niet.
            // Dus conceptueel eigenlijk even /* thisfield = {idCounter: idCounter, kkenmerktype: "l"  }; */
            thisfield_auto = { idCounter: idCounter
                             , kkey: 0
                             , kkeycount: 0
                             , kkenmerktype: "l"
                             , isShowExpr: false
                             }
            kv_data = kenmerktype_blocktitel(thisfield_auto, props, params);
            kv = kv_data.lkv;
            blockId = idCounter;
            blockShow["flx_" + props.nameprefix + blockId] = 0;
            // Om er een veld van te maken zou je kv2html(..) doen,
            // maar ik weet dat die voor blokken gewoon de parameter teruggeeft
            kenmerk_write(kv);
        }

        switch (thisfield.kkenmerktype)
        {
            case "C":
                kv = kenmerktype_text(thisfield, props, params);
                anyTextarea = anyTextarea || (thisfield.klengte > 50 && !props.kenmerk_search && !thisfield.readonlyfield && !thisfield.maskeren);
                break;
            case "D":
                kv = kenmerktype_date(thisfield, props, params, dpYearRanges);
                anyDate = anyDate || !thisfield.maskeren;
                break;
            case "T":
                kv = kenmerktype_time(thisfield, props, params);
                anyTime = anyTime || !thisfield.maskeren;
                break;
            case "r":
            case "R":
                kv = kenmerktype_listbox(thisfield, props, params);
                anySelect = anySelect || (!thisfield.readonlyfield && !thisfield.maskeren);
                break;
            case "S":
                kv = kenmerktype_suggest(thisfield, props, params);
                break;
            case "V":
                kv = kenmerktype_checkbox(thisfield, props, params);
                thisfield.separator = "";
                break;
            case "N":
                kv = kenmerktype_number(thisfield, props, params);
                break;
            case "X":
                kv = kenmerktype_xref(thisfield, props, params);
                break;
            case "L":
                kv = kenmerktype_label(thisfield, props, params);
                thisfield.separator = "";
                break;
            case "l":
            case "b":
                if (!props.noblock)
                {
                    kv_data = kenmerktype_blocktitel(thisfield, props, params);
                    kv = kv_data.lkv;
                    anyCollapsible = anyCollapsible || (!props.reado && thisfield.forceReadonly)
                    blockId = thisfield.idCounter;
                    blockShow["flx_" + props.nameprefix + blockId] = 0;
                    if (thisfield.kkenmerktype == "b")
                      anyTreatable = anyTreatable || anyCollapsible;
                }
                break;
            case "F":
            case "E":
                kv_data = kenmerktype_bijlage(thisfield, props, params);
                kv = kv_data.lkv;
                addPreview = props.reado && kv_data.addPreview;
                kotherkey = kv_data.kotherkey;
                anyMobileBijlagen = anyMobileBijlagen || (props.mobile && thisfield.kdimensie != 'xxx');
                break;
            case "M":
                params.attach_km_list = attach_km_list;
                kv_data = kenmerktype_folder(thisfield, props, params);
                if (thisfield.otherPath && !inArray(thisfield.otherPath, attach_km_list)) // Voeg een bijlagenmap alleen toe als deze nog niet in het array zit.
                    attach_km_list.push(thisfield.otherPath);      // Ja, en wel NA aanroep van kenmerk_folder. Anders gaat het huidige pad toch mee in params.attach_km_list!
                kv = kv_data.lkv;
                addPreview = props.reado && kv_data.addPreview;
                anyMobileBijlagen = anyMobileBijlagen || (props.mobile && thisfield.kdimensie != 'xxx');
                break;
            case "Q":
                kv = kenmerktype_spacer(thisfield, props, params);
                break;
        }
        anyHint = anyHint || (thisfield.kkenmerktype != "l" && thisfield.kkenmerktype != "b" && thisfield.kkenmerktype != "Q" && kv && thisfield.khint != null && !thisfield.ishidden && !props.nolabel);
        if ((thisfield.kkenmerktype != "l" && thisfield.kkenmerktype != "b" && kv.length > 0) ||
            (thisfield.kkenmerktype == "b" && kv_data.show_blok))
        {   // Verhoog het aantal ingevulde kenmerken in een blok.
            blockShow["flx_" + props.nameprefix + blockId]++;
        }
        // vangnet: verbergen ondanks alles (kan ook vooraf, is slimmer)
        thisfield.ishidden      = (props.fnIsHidden && props.fnIsHidden(thisfield.krolcode, thisfield.kdefault));

        // ==========================================================================================
        // Alles bepaald, te tonen waarde/veld in kv, nu positioneren enzo in kvhtml
        // ==========================================================================================
        kenmerk_write("\n<!-- Kenmerk {0}:{1} '{2}' -->\n".format(thisfield.kkey, thisfield.kkenmerktype, thisfield.komschrijving));

        // Voor een multi-value waarde maken we een array, denk aan 3 bestandsnamen,
        kv_arr.push({label: ""/*geen toevoeging aan het label*/, waarde: kv});

        var kvhtml = "00";  // hierin bouwen we de html op

        for(kv_i=0; kv_i< kv_arr.length; kv_i++)
        {
            var kvparams = { kcount: thisfield.kkeycount
                           , forceNewLine: forceNewLine
                           , colCount: colCount
                           , kenmerk_2col: kenmerk_2col
                           , props: props  /* de rest zeg maar */
                           , module: module
                           , kotherkey: kotherkey
                           , clabelCounter: clabelCounter
                           , addPreview: addPreview
                           , kv: kv_arr[kv_i].waarde
                           , label_ext: kv_arr[kv_i].label
                           , hasAnyFlexExpr: hasAnyFlexExpr
                           , isParent: isParent
                           };

            kvhtml = kv2html(thisfield, kvparams);
            hasfilter_cnt = hasfilter_cnt + (kvhtml == "" ? 0 : 1);
            //kvhtml = "<tr><td>"+thisfield.kkey+"/"+thisfield.komschrijving+"</td><td>"+kv_arr[kv_i].waarde+"</td></tr>";  //testshortcut

            // Suppress label in SHOW mode when the label has a ##SHOW## expression and the exprwaarde == false
            var suppress_label =
                show_label_expr[thisfield.kkey] == 0 ||
                (
                    props.reado &&
                    thisfield.kshow_expr &&
                    pkey &&
                    (
                        thisfield.kkenmerktype == "L" ||
                        (
                            thisfield.kkenmerktype != "l" &&
                            thisfield.kkenmerktype != "b" &&
                            thisfield.kdefault
                        )
                    ) &&
                    !evaluateExpression(
                        pkey,
                        thisfield.kshow_expr,
                        module == "MLD" && props.niveau == "O" ? "OPD" : module,
                        props.kenmerk_module || props.niveau || null
                    )
                );

            // En hier schrijven we het hele veld dan naar de pagina
            if (!suppress_label)
                kenmerk_write (kvhtml);

            idCounter++; // Nu ophogen
        }
        oRs.MoveNext();

        // Collapse code er achteraan als de *volgende* een ander label is @@TODO
        if (!thisfield.ishidden && (inMobileBlock || (!props.mobile && allCollapsable)) && (oRs.Eof || oRs("kenmerk_kenmerktype").Value.toUpperCase() == 'L')) // einde huidige label
        {
            if (props.mobile)
            {
                kenmerk_write('</div>');
                inMobileBlock = false;
                if (!allCollapsable) // the current collapseBlock should be expanded
                    kenmerk_write('<script> $(function(){ $("#collLabel'+clabelCounter+'").trigger("click"); });</script>');
            }
            else
                kenmerk_write('<script>$(function(){$("#' + props.nameprefix + 'clabel' + clabelCounter + '").addClass("flexcollapsed").nextUntil($("#' + props.nameprefix + 'clabel' + (clabelCounter + 1) + '")).hide();})</script>');

            allCollapsable = false;
        }
    }
    // Alle i flexkenmerken doorlopen.
    // ============================================================================================
    if (hasAnyKenmerk)
        oRs.close();

    // All fields are done here

    if (props.kenmerk_search)
    {
        if (hasfilter_cnt == 0)
        {
            kenmerk_write("<tr><td>" + L("lcl_shared_advanced_none") + "</td></tr>");
        }
        kenmerk_write("</table>");
    }

    // Maak kenmerkblokken die geen gegevens bevatten onzichtbaar.
    for (sb in blockShow)
    {
        if (blockShow[sb] == 0)
        { %>
        <script>
            $("#<%=sb%>").hide();
        </script>
<%      }
    }

    if (/*!thisfield.ishidden && PF:deze bestaat hier niet meer */ inMobileBlock || (!props.mobile && allCollapsable)) // allerlaatste ook nog
    {
        if (props.mobile)
        {
            kenmerk_write('</div>');
            if (!allCollapsable) // the current collapseBlock should be expanded
              kenmerk_write('<script> $(function(){ $("#collLabel'+clabelCounter+'").trigger("click"); });</script>');
        }
        else
            kenmerk_write('<script>$(function(){$("#' + props.nameprefix + 'clabel' + clabelCounter + '").addClass("flexcollapsed").nextUntil($("#' + props.nameprefix + 'clabel' + (clabelCounter + 1) + '")).hide();})</script>');
    }

    var counterName = (props.extraserie? props.nameprefix + "_all"    : "k_all");
    var flexsqlName = (props.extraserie? props.nameprefix + "_flexsql": "k_flexsql");
    if (!props.mobile)
    {
        if (!props.notr)
            kenmerk_write('<tr>');
        kenmerk_write('<td>');
    }

    if (hasAnyKenmerk && props.flexstart == 1) // Deze hoeft maar 1 keer.
    {   // Toelichting: In bez_edit_bezoekers.inc wordt load_kenmerk.asp twee keer achter elkaar aangeroepen.
        //              Een keer voor kenmerken die men achter de bezoeker op dezelfde regel wil hebben staan
        //              en een keer voor de rest van de kenmerken die in een <div> onder de bezoeker komen te staan.
        //              Om te voorkomen dat beide keren de id's k_all, k_flexsql en ktmpfolder worden aangemaakt alleen de eerste keer deze aanmaken.

        if (pkey < 0 || props.copy)
            kenmerk_write("<input type='hidden' name='ktmpfolder" + props.serie + "' id='ktmpfolder" + props.serie + "' value='" + props.tmpfolder + "'>");

        kenmerk_write('<input type="hidden" id="' + counterName + '" name="' + counterName + '" value="' + (idCounter - 1) + '">');
        kenmerk_write('<input type="hidden" id="' + flexsqlName + '" name="' + flexsqlName + '" value="' + (prssql? 1 : 0) + '">');
        kenmerk_write('<input type="hidden" name="nameprefix" value="' + props.nameprefix + '">');
    }

    if (!props.mobile)
    {
        kenmerk_write('</td>');
        if (!props.notr)
            kenmerk_write('\n</tr>');
    }

    // Wat 'globale' data voor calculateFlexExpressions()
    if (hasAnyFlexExpr) { %>
        <input type="hidden" id="<%=props.nameprefix%>_data"
                data-any-show="<%=hasAnyShowExpr ? 1 : 0%>"
                data-module="<%=module%>"
                data-refkey="<%=pkey%>"
                data-niveau="<%=props.kenmerk_module%>"
                data-extraserie="<%=props.extraserie ? props.extraserie : 0%>">
<%  }

    if (__insideblock > 0) {
        kenmerk_write(BLOCK_END_HTML());
        __insideblock = 0;
    }

    // Eventuele scripts moeten buiten de </table>
    if (anyCollapsible && !props.mobile && !props.kenmerk_search)
    {
        %><script>
        $(function() {
            $(".card[id^=flx_<%=props.nameprefix%>] > [data-toggle=collapse]").on("click", function () {
                let $target = $($(this).data("target"));
                $(this).attr("aria-expanded", ($(this).attr("aria-expanded") == "false" ? "true" : "false"));
                $target.toggle().promise().done(() => {
                    FcltMgr.resized();
                    $target[0].scrollIntoView({ block: "nearest", behavior: "smooth" });
                });
            });
        });
        </script><%
    }

    if (anyTextarea && !props.kenmerk_search)
    {
        %><script>
        $(function()
        {
            $("textarea[name^=<%=props.nameprefix%>]").on("resize", function () { FcltMgr.resized(window); });
            if (<%=(props.mobile ? 0 /* Niet bij mobile */ : 1)%> && $.fn.autogrow)
                $("textarea[name^=<%=props.nameprefix%>]").autogrow();
        });
      </script><%
    }

    if (anySelect && props.kenmerk_search)
    {
        %><script>
        $(function()
        {
           $("select[name^=<%=props.nameprefix%>][advmulti=1]").multiSelectToggle({advanced: true});
        });
      </script><%
    }

    if (anyTreatable)
    {
        %><script>
        $(function()
        {
           $(".span_treatable").on("click", e => FcltMgr.stopPropagation(e));
        });
      </script><%
    }

    if (anyHint)
    {
        %><script>
        $(function() {
            if (Modernizr.pointerevents || Modernizr.touchevents) {
                $("label[for^=<%=props.nameprefix%>]").parent("td.hashint").find("span.hint").on("contextmenu", function (e) { e.preventDefault(); })
                                                                                             .on("touchstart", () => {});
            }
        });
      </script><%
    }

    if (anyMobileBijlagen)
    {
        %>
        <div class="modal fade" id="imagemodal" tabindex="-1" role="dialog" aria-hidden="true">
            <div class="modal-dialog modal-dialog-centered">
                <div class="modal-content">
                    <div class="modal-body">
                        <img src="" class="imagepreview" style="width: 100%;">
                    </div>
                </div>
            </div>
        </div>
        <script>
            $(function() {
                $("a.previewext").on("click", function(event) {

                    event.preventDefault();

                    $(".imagepreview").attr("src", this.href);
                    $("#imagemodal").modal("show");
                });
            });
        </script><%
    }

    if (props.kenmerk_search)
    {
        %><script>
        $(function()
        {
          $("select.operand").each(function() {

            $(this).change(function() {

              var isIs = $(this).val() == "<>" || $(this).val() == "=";
              var $advDiv = $(this).next("div.advmultitoggle:has(> select[multiple])");       // open multiToggle

              if (!isIs && $advDiv.length)
                $advDiv.find("span.multi_select_toggle, span.multi_suggest_toggle").click();  // close open multiToggle

              $(this).next("div.advmultitoggle")
                     .find("span.multi_select_toggle, span.multi_suggest_toggle")
                     .toggleClass("disabled", !isIs);
            });
          });
        });
      </script><%
    }

    // Bij template definitie onderdrukken we ##SHOW##-expressies
    // Bij het instantieren van een template (&template=1) niet
    if (!getQParamInt("template", 0) && getQParamInt("act_key", -1) > -1) {
        %><script>
        $(function()
        {
            window.calculateFlexExpressions = _ => {
                return true; // Templates onderdrukken niets obv ##SHOW##-expressies
            }
        });
      </script><%
    }

    // Bij kenmerk_search doen de load_kenmerk's geen FCLTHeader.Generate en dus hebben
    // we date.js niet gegarandeerd. Dates werd echter hieronder toch al niet
    // meegenomen dus komen we er mee weg gewoon hier geen kalenders te doen
    if (anyDate && !props.kenmerk_search)
    {
        // FCLTHeader.Requires is hier te laat
        // Zie ook calendar.inc. Delen code was iets te lastig
        %><script type="text/javascript">
        $(function()
        {
<%          /* Onderstaande staat ook in kenmerk_js.asp */ %>
            var touchdevice = (typeof window.orientation !== "undefined") || (typeof window.ontouchstart !== "undefined") || (navigator.userAgent.indexOf('IEMobile') !== -1);
            if ((typeof Modernizr == "undefined" || !Modernizr.inputtypes.date || !touchdevice) && $.datepicker)
            {
                $.datepicker._gotoToday = function(id) {
                    $(id).datepicker("setDate", new Date()).datepicker("hide").trigger("change").blur();
                };
                // jQuery datepicker
                $("input[name^=<%=props.nameprefix%>].flddate").not("[readonly]").datepicker({
                    showOn: "none",
                    showButtonPanel: true,
                    currentText: L("lcl_date_today"),
                    closeText: L("lcl_cancel"),
                    duration: 'fast',
                    dateFormat: 'dd-mm-yy', // zo verwacht de flex-code het.
                    changeYear: true,
                    changeMonth: true,
                    beforeShow: function(input, inst) { setTimeout("FcltMgr.resized(window)", 100) },
                    onUpdateDatepicker: function() { $(".ui-datepicker-current").removeClass("ui-priority-secondary").addClass("ui-priority-primary"); },
                    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%>");
<%
                }
%>
            }
            else
            {
              // Native date input field
              $("input[name^=<%=props.nameprefix%>].flddate").not("[readonly]").each(function() {
                    // Native, so change date format from [DD-MM-YYYY] -> [YYYY-MM-DD] (native format)
                    $(this).val($(this).attr("value").split("-").reverse().join("-"));
                    // Make this a native datepicker
                    $(this).attr("type", "date");
                    // Change hidden type field from 'D' to 'DN'
                    var nativeId = $(this).attr("id").slice(0, -3);
                    $("input:hidden#"+nativeId+"t").val("DN");
              });
            }
            FcltMgr.resized();
        });
      </script><%
    }

    if (anyTime && !props.kenmerk_search)
    {
        // FCLTHeader.Requires is hier te laat
        %><script type="text/javascript">

        var minMax = minMax || {};
        $(function()
        {
            var touchdevice = (typeof window.orientation !== "undefined") || (typeof window.ontouchstart !== "undefined") || (navigator.userAgent.indexOf('IEMobile') !== -1);
            if (typeof Modernizr == "undefined" || !Modernizr.inputtypes.time || !touchdevice)
            {
<%              for (i = 0; i < minMax.length; i++)
                { %>
                $.extend(minMax, {"<%=minMax[i].namePrefix + minMax[i].id%>": {"instantiated": false, "startTime": "<%=minMax[i].kmin%>", "endTime": "<%=minMax[i].kmax%>"}} );
<%              } %>
                var entry;
                $("input[name^=<%=props.nameprefix%>].fldtime").not("[readonly]").each(function()
                {
                    entry = $(this).attr("id").slice(0, -3);
                    if (entry in minMax && !minMax[entry].instantiated)
                    {
                        $(this).timePicker({
                            step: 15,
                            startTime : minMax[entry].startTime,
                            endTime   : minMax[entry].endTime,
                            columns   : <%=S("timepickercolumns")%>,
                            onChange  : function () {
                              $timeInput = $(this).parents("div.time-holder").prev("input[name^=<%=props.nameprefix%>].fldtime")
                              $timeInput.removeClass("bad");
                              $timeInput.trigger("change");
                            }
                        });
                        minMax[entry].instantiated = true;
                    }
                });
            }
            else
            {
              // Native timepicker
              $("input[name^=<%=props.nameprefix%>].fldtime").not("[readonly]").each(function() {
                  // Make this a native timepicker
                  $(this).attr("type", "time");
              });
            }
        });
      </script><%
    }

    if (props.hasfilter && hasfilter_cnt > 5)
    {
%>
    <script>
        $(function ()
        {
            $(".fcltblocktab").eq(0).before("<div class='advanced-header'><span class='advanced-header-text'>" + L("lcl_flexible_properties") + "</span><input id='autofilter' class='autofilter_<%=props.nameprefix%>' type='text' placeholder='"+safe.htmlattr(L("lcl_autofilter"))+"'></div>");
            $(".autofilter_<%=props.nameprefix%>")
                .click(function(e) {e.stopPropagation()})
                .keyup(function(e) {
                    if (e.which == 27) {
                        $(".autofilter_<%=props.nameprefix%>").val("");
                    }
                    delayed(200, doFilter.bind(null, $(".autofilter_<%=props.nameprefix%>")), "<%=props.nameprefix%>");
                });
        });

        function delayed(delay, fn, nameprefix)
        {
            if (window["timerID_" + nameprefix])
                clearTimeout(window["timerID_" + nameprefix]);
            window["timerID_" + nameprefix] = setTimeout(fn, delay);
        }

        function doFilter(deze)
        {
            var valThis = deze.val().toLowerCase();
            var $flex_table = deze.closest(".advanced-header").next(".fcltblocktab");
            $flex_table.find(":not(.times) > tr").each(function () {
                var text = $(this).find("td.label > label").text().toLowerCase();
                $(this).toggle(text.indexOf(valThis) > -1);
            });
        }
    </script>
<%
    }

    if (props.mobile && clabelCounter)
    { %>
      <script>
        $(function ()
        {
          for (var i = 0; i <= <%=clabelCounter%>; i++)
          {
            if ($("h3#collLabel"+i).next("div.ui-collapsible-content") &&
                $("h3#collLabel"+i).next("div.ui-collapsible-content").children().length == 0)
            {
              // Change empty collapsable mobile labels back to regular labels
              $("h3#collLabel"+i).unwrap()
                                 .contents().unwrap();
            }
          }
        });
      </script>
<%  }    if (props.multiMode)
    { %>
      <script type="text/javascript">

        $(function()
        {
          init_multi_flex();
        });

      </script>
<%  }
    // remove fac_kenmwerkwaarden to keep the file clean
    if (hasAnyKenmerk)
        removekenmerkwaarden(module, pkey);

    if (props.getTmpfolder)
      return props.tmpfolder;  // ik vind dit best een eigenaardige returnvalue
}


// Ondersteunende filehandlingfuncties ============================================================
// Hernoem folders onder basepath
// Flexsql levert twee velden op: oude kenmerk key en nieuwe key
function RenameFlexFolders(flexsql, pModule, pNiveau, pKey)
{
  var basepath = pModule + "/" + subfolderKey(pNiveau, pKey)

  var oRs = Oracle.Execute(flexsql);
  while (!oRs.Eof)
  {
    var basepath_old = basepath + "/" + oRs(0).Value;
    var basepath_new = basepath + "/" + oRs(1).Value;

    var vAttachPath  = S("flexFilesPath") + "/" + basepath_old;
    var vAttachPath2 = S("flexFilesPath") + "/" + basepath_new;
    MoveFlexfiles(vAttachPath, vAttachPath2);
    //
    // fac_bijlagen tabel bijwerken.
    var sql_u = "UPDATE fac_bijlagen"
              + "   SET fac_bijlagen_disk_directory = " + safe.quoted_sql(basepath_new)
              + "     , fac_bijlagen_kenmerk_key = " + oRs(1).Value
              + " WHERE fac_bijlagen_key = fac_bijlagen_root_key"
              + "   AND fac_bijlagen_module = " + safe.quoted_sql(pModule)
              + "   AND fac_bijlagen_refkey = " + pKey
              + "   AND fac_bijlagen_kenmerk_key = " + oRs(0).Value;
    Oracle.Execute(sql_u);
    oRs.MoveNext();
  }
  oRs.Close();
}

// Copy selected files from  oldbasepath to newbasepath
// Flexsql levert twee velden op: oude kenmerkkey en nieuwe kenmerkkey
function CopyFlexFiles(pmodule, poldniveau, poldkey, pnewniveau, pnewkey, flexsql, filter)
{
  var oRs = Oracle.Execute(flexsql);
  // Only one from kenmerk_key and one to kenmerk_key combination is expected
  if (!oRs.Eof)
  {
    var old_km_key = oRs(0).Value;
    var new_km_key = oRs(1).Value;

    vAttachPath  = S("flexFilesPath") + "/" + pmodule + "/" + subfolderKey(poldniveau, poldkey) + "/" + old_km_key;
    vAttachPath2 = S("flexFilesPath") + "/" + pmodule + "/" + subfolderKey(pnewniveau, pnewkey) + "/" + new_km_key;
    __Log("Kopie files " + vAttachPath + " Naar " + vAttachPath2);

    var sql_c = "SELECT r.fac_bijlagen_key"
              + "     , r.fac_bijlagen_disk_directory"
              + "  FROM fac_bijlagen c"
              + "     , fac_bijlagen r"
              + " WHERE c.fac_bijlagen_root_key = r.fac_bijlagen_key"
              + "   AND c.fac_bijlagen_verwijder IS NULL"
              + "   AND c.fac_bijlagen_refkey = " + poldkey
              + "   AND c.fac_bijlagen_kenmerk_key = " + old_km_key
              + "   AND c.fac_bijlagen_module = " + safe.quoted_sql(pmodule);
    if (filter.length)
    {
        sql_c += "  AND c.fac_bijlagen_filename IN (" + safe.quoted_sql_join(filter) + ")";
    }
    var oRs_c = Oracle.Execute(sql_c);
    while (!oRs_c.eof)
    {
        var vAttachPath0 = S("flexFilesPath") + "/" + oRs_c("fac_bijlagen_disk_directory").Value;
        if (vAttachPath0 != vAttachPath)
        {
            __Log("kopie via verwijzing van: " + vAttachPath0);
        }
        var sql = "BEGIN "
                + "   flx.copyflexbijlage(" + safe.quoted_sql(pmodule) + ", " + pnewkey + ", " + new_km_key + ", " + oRs_c("fac_bijlagen_key").Value + ");"
                + " END;";
        var err = Oracle.Execute(sql, true);
        if (err.friendlyMsg)
        {
            __SafeDoLog("Copying files from " + vAttachPath0 + "<br>to " + vAttachPath2 + " failed:<br>" + err.friendlyMsg, "#FFFF00");
        }
        oRs_c.MoveNext();
    }
    oRs_c.Close();
  }
  oRs.Close();
}

// Kopieer folders van oldbasepath naar newbasepath
// Flexsql levert twee velden op: oude key en nieuwe key
function CopyFlexFolders(pmodule, poldniveau, poldkey, pnewniveau, pnewkey, flexsql)
{   // Bestanden worden niet echt gekopieerd maar er wordt een entry met referentie (root_key) naar het oorpronkelijke bestand gemaakt.
    //"FIN/" + subfolderKey("F", copyfin_key), "FIN/" + subfolderKey("F", fin_key)
    var oRs = Oracle.Execute(flexsql);
    while (!oRs.Eof)
    {
        var old_km_key = oRs(0).Value;
        var new_km_key = oRs(1).Value;

        vAttachPath  = S("flexFilesPath") + "/" + pmodule + "/" + subfolderKey(poldniveau, poldkey) + "/" + old_km_key;
        vAttachPath2 = S("flexFilesPath") + "/" + pmodule + "/" + subfolderKey(pnewniveau, pnewkey) + "/" + new_km_key;
        __Log("Kopie map " + vAttachPath + " Naar " + vAttachPath2);

        var sql_c = "SELECT r.fac_bijlagen_key"
                  + "     , r.fac_bijlagen_disk_directory"
                  + "  FROM fac_bijlagen c"
                  + "     , fac_bijlagen r"
                  + " WHERE c.fac_bijlagen_root_key = r.fac_bijlagen_key"
                  + "   AND c.fac_bijlagen_verwijder IS NULL"
                  + "   AND c.fac_bijlagen_refkey = " + poldkey
                  + "   AND c.fac_bijlagen_kenmerk_key = " + old_km_key
                  + "   AND c.fac_bijlagen_module = " + safe.quoted_sql(pmodule);
        var oRs_c = Oracle.Execute(sql_c);
        while (!oRs_c.eof)
        {
            var vAttachPath0 = S("flexFilesPath") + "/" + oRs_c("fac_bijlagen_disk_directory").Value;
            if (vAttachPath0 != vAttachPath)
            {
                __Log("kopie via verwijzing van: " + vAttachPath0);
            }
            var sql = "BEGIN "
                    + "   flx.copyflexbijlage(" + safe.quoted_sql(pmodule) + ", " + pnewkey + ", " + new_km_key + ", " + oRs_c("fac_bijlagen_key").Value + ");"
                    + " END;";
            var err = Oracle.Execute(sql, true);
            if (err.friendlyMsg)
            {
                __SafeDoLog("Copying files from " + vAttachPath0 + "<br>to " + vAttachPath2 + " failed:<br>" + err.friendlyMsg, "#FFFF00");
            }
            oRs_c.MoveNext();
        }
        oRs_c.Close();
        oRs.MoveNext();
    }
    oRs.Close();
}


// ================================================================================================
// Save de kenmerken die we met listKenmerk hebben aangemaakt
//
// 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 strak-gedefinieerde kolommen
//                       - 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. kk en kv zullen er dan ook altijd beide zijn
// Nieuw is dat we af moeten blijven van confidential kenmerken, dat zijn kenmerken die
//  vertrouwelijk zijn (systeem&4) gedefinieerd en voor deze user afgeschermd zijn (confidential)
// ================================================================================================
// Let op: de voorkeur is het gebruik van saveFlexKenmerken(parentKey, params)
//         voor backward compatibiliteit 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,
                           kenmerkToonbaar,
                           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,
                          kenmerkToonbaar: kenmerkToonbaar,
                          currentKenmerkenSQL: currentKenmerkenSQL,
                          requestQF: RequestQ?Request.QueryString:Request.Form,
                          moduleName: moduleName,
                          moduleVal: moduleVal,
                          isNew: isNew,
                          flexPath: flexPath,
                          multiMode: multiMode //waar komt deze vandaan>
                        }
  }

  var flexdata = kenmerkParams.flexdata;
  if (!flexdata)
  {
    // Maak alsnog het flexdata object: backwards compatible!
    var params = {};
    if (kenmerkParams.allKenmerkenSQL) params.allKenmerkenSQL = kenmerkParams.allKenmerkenSQL;
    if (kenmerkParams.extraserie) params.extraserie = kenmerkParams.extraserie;
    if (kenmerkParams.nameprefix) params.nameprefix = kenmerkParams.nameprefix;
    if (kenmerkParams.multiMode) params.multiMode = kenmerkParams.multiMode;
    if (kenmerkParams.flexPath) params.flexPath = kenmerkParams.flexPath;
    if (kenmerkParams.multiEdit) {
        params.multiEdit = kenmerkParams.multikenmerk;
        if (kenmerkParams.multikenmerk) params.multikenmerk = kenmerkParams.multikenmerk;
    }
    flexdata = flexkenmerken2jsondata(params);
  }
  return _saveFlexKenmerken(parentKey, kenmerkParams, flexdata);
}
function _saveFlexKenmerken(parentKey, params, flexdata) // TODO: support multiMode!
{
  if (parentKey <= 0) return; // Er moet wel een geldige parentKey meegegeven zijn.

  var kenmerk_keys = {}; // 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 ontstaan 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)
    {
      var fac_functie_key = oRs("fac_functie_key").Value;
      var forceReadonly = false;
      if (fac_functie_key)
      { // Er is een extra autorisatie ingesteld die nodig is om het kenmerk te kunnen lezen en/of schrijven.
        var additAuthrights = user.checkAutorisation(fac_functie_key, true); // Additional Authorization rights.
        forceReadonly = !additAuthrights || (additAuthrights && additAuthrights.ALGwritelevel > 5 && additAuthrights.PRSwritelevel > 5); // Zijn er schrijfrechten op de extra autorisatie?
      }
      kenmerk_keys[oRs(params.kenmerkKey).Value] = { waarde: oRs(params.kenmerkWaarde).Value,            // kenmerkwaarde moet oldval bevatten
                                                     readonly: oRs(params.kenmerkToonbaar).Value == 1 || forceReadonly }; // Kenmerk is "Niet te wijzigen": 1=Niet te wijzigen, NULL=Te wijzigen.
      oRs.MoveNext();
    }
    oRs.Close();
  }

  if (params.isNew && !params.noAttachments) // uploads oude stijl (mld_edit_melding_save.asp en consorten)
  {
    var ktmpfolder = getFParam("ktmpfolder" + (params.tmpfolderNum? params.tmpfolderNum : 0), "");
    if (ktmpfolder)
    {
      var tmp_folder =  "__NEW__" + user_key + "_" + safe.filename(ktmpfolder);
      vAttachPath = shared.tempFolder() + "/" + params.flexPath + tmp_folder;
      var flexPathArray = params.flexPath.split("/"); // MLD/M => MLD (0) en M (1)
      var newfolder = flexPathArray[0] + "/" + subfolderKey(bepaalNiveau(params.moduleName, flexPathArray[1]), parentKey);

      vAttachPath2 = S("flexFilesPath") + "/" + newfolder;
      // 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);
          // MoveFolder werkt niet als flexfilespath op een andere drive of zelfs share staat
          // Daarom maar in een loopje stuk voor stuk moven. We weten dat de folder exact
          // één niveau diep zijn
          var tempfolder = fso.GetFolder(vAttachPath);
          var fc = new Enumerator(tempfolder.SubFolders);
          for (; !fc.atEnd(); fc.moveNext())
          {
              var folder = fc.item();
              __Log("Moving: " + folder.Name);
              CreateFullPath(vAttachPath2 + "\\" + folder.Name);
              fso.MoveFile(vAttachPath + "\\" + folder.Name + "\\*.*", vAttachPath2 + "\\" + folder.Name);
          }

          // Definitieve foldernaam en refkey voor fac_bijlagen bijwerken
          var sql = "UPDATE fac_bijlagen"
                  + "   SET fac_bijlagen_refkey = " + parentKey
                  + "     , fac_bijlagen_disk_directory = REPLACE(fac_bijlagen_disk_directory, {0}, {1})"
                                            .format(safe.quoted_sql(params.flexPath + tmp_folder), safe.quoted_sql(newfolder))
                  + " WHERE fac_bijlagen_refkey = -1"
                  + "   AND fac_bijlagen_disk_directory LIKE " + safe.quoted_sql(params.flexPath + tmp_folder + "%");
          Oracle.Execute(sql);
          fso.DeleteFolder(vAttachPath);
        }
        catch (ee)
        {
          __DoLog("Move files uit Folder " + vAttachPath + " naar " + vAttachPath2 + " nog mislukt: " + ee.description);
        }
      }
      fso = null;
    }
  }
  // Uploads nieuwe stijl (pda die via API2 geeft geupload)
  if (params.isNew)
  {
    for (var i = 1; i < 100; i++)
    {
      if (getFParam("attachments[" + i + "][filename]", "")) // de originele filenaam, doen we niets mee
      {
        // elk nieuw bestand zit in een eigen flexfiles\MLD\M__NEW__81968_mG5Yj9duWAwuK16XYYbKwoS3RfJOYCL6\2961
        var ktmpfolder = getFParam("attachments[" + i + "][token]", "");
        if (ktmpfolder)
        {
          var tmp_folder =  "__NEW__" + user_key + "_" + safe.filename(ktmpfolder);
          var vAttachPathSource = shared.tempFolder() + "/" + params.flexPath + tmp_folder;
          var flexPathArray = params.flexPath.split("/"); // MLD/M => MLD (0) en M (1)
          var vAttachSubPath = flexPathArray[0] + "/"
                                + subfolderKey(bepaalNiveau(params.moduleName, flexPathArray[1]), parentKey);
          var vAttachPathTarget = S("flexFilesPath") + "/" + vAttachSubPath;
    	    var fso = Server.CreateObject("Scripting.FileSystemObject");
          if (fso.FolderExists(vAttachPathSource))
          {
            if (!fso.FolderExists(vAttachPathTarget)) // nieuwe hoofdfolder is er nog niet
              CreateFullPath(vAttachPathTarget); // het 1000-tal was er misschien ook nog niet.

            // nu bestaat voor een melding 1021481 de folder flexfiles\MLD\M1021___\M1021481
            // Misschien bestaat flexfiles\MLD\M1021___\M1021481\2803 ook al
            try
            {
              var oFolder = fso.GetFolder(vAttachPathSource); // Doorloop de kenmerk keys (zal er *eentje* zijn)
              var fc1 = new Enumerator(oFolder.SubFolders);
              for (; !fc1.atEnd(); fc1.moveNext())
              {
                var folder = fc1.item(); // folder.Name is eigenlijk kenmerk_key
                var fromFolder = vAttachPathSource + "/" + folder.Name;
                var toFolder = vAttachPathTarget + "/" + folder.Name;
                if (!fso.FolderExists(toFolder)) // kenmerkfolder is er nog niet
                  CreateFullPath(toFolder);
                __Log("Verplaats inhoud " + fromFolder + "/*.* naar " + toFolder + "/");
                fso.MoveFile (fromFolder + "/*.*", toFolder + "/");

                var sql = "UPDATE fac_bijlagen"
                        + "   SET fac_bijlagen_refkey = " + parentKey
                        + "     , fac_bijlagen_disk_directory = REPLACE(fac_bijlagen_disk_directory, {0}, {1})"
                                                  .format(safe.quoted_sql(params.flexPath + tmp_folder), safe.quoted_sql(vAttachSubPath))
                        + " WHERE fac_bijlagen_refkey = -1"
                        + "   AND fac_bijlagen_disk_directory LIKE " + safe.quoted_sql(params.flexPath + tmp_folder + "%");
                Oracle.Execute(sql);
              }
              fso.DeleteFolder(vAttachPathSource);
            }
            catch (ee)
            {
              __DoLog("MoveFlexfiles " + vAttachPathSource + " naar " + vAttachPathTarget + " mislukt: " + ee.description);
            }
          }
          fso = null;
        }
      }
    }
  }

  var trackarray = []; // eventuele tracking
  var hasExpression = false;
  var notify_disc = [];
  var notify_prs = [];
  var blokkenmerk_key_arr = [];
  for (kenmerkkey in flexdata)
  {
    var flex = flexdata[kenmerkkey];
    var kenmerk_key = flex.id;
    if (flex.type == "b" && params.module == "MLD")
    { // Behandelbare bloktitel.
      // Alleen meldingen bebben behandelbare bloktitel kenmerken.
      // Deze afhandelen in een eigen mld procedure. Zo blijft deze functie saveFlexKenmerken verder algemeen.
      var mldtracking = mld.saveFlexBlokKenmerken(parentKey, flex, params); // Behandelbare bloktitel kenmerken afhandelen tijdens het opslaan van de melding.
      trackarray = trackarray.concat(mldtracking);
      blokkenmerk_key_arr.push(kenmerk_key);
    }

    if (kenmerk_keys[kenmerk_key])
        if ( flex.type == "M" ||                                            // Die zijn al lang geregistreerd in UploadForm_save.asp
             (!params.isNew && (flex.type == "F" || flex.type == "E")) ||   // Deze zijn al geregistreerd via AjaxSaveFlex.asp
             kenmerk_keys[kenmerk_key] && kenmerk_keys[kenmerk_key].readonly)       // Alleen wijzigbare flexkenmerken kunnen opgeslagen worden.
          continue;

    var vold = (kenmerk_keys[kenmerk_key] && !!kenmerk_keys[kenmerk_key].waarde)? kenmerk_keys[kenmerk_key].waarde : "";
    hasExpression = hasExpression || flex.expr == 1;
    var vnew = flex.value || "";

    if (flex.type == "N" && flex.dec > 0 && (params.relativeValuesAllowed || params.multiMode))
    {
      if (vold == "")
      {
          flex.value = calculatePrice(0, vnew).toString();
      }
      else
      {
          flex.value = calculatePrice(vold, vnew).toString();
      }

      // Alleen nieuwe waarden die binnen de min en max vallen kunnen opgeslagen worden.
      // De controle is niet gebeurt bij invoer met een "%" of "++" of "--".
      if (((flex.min != "") && (flex.value < flex.min)) ||
          ((flex.max != "") && (flex.value > flex.max)))
        continue;

      vnew = flex.value.toString();
    }

    // tracking?
    if (!params.isNew && params.tracking)
    {
      if (vold != vnew)
      {
        // allKenmerkenSQL levert nu deze kolommen (deels future use):
        //  kenmerk_omschrijving
        //  kenmerk_kenmerktype
        //  kenmerk_systeem
        //  kenmerk_readonly
        //  kenmerk_uniek
        //  fac_kenmerkdomein_key
        var oRsk = Oracle.Execute(params.allKenmerkenSQL + kenmerk_key);
        var label       = oRsk("kenmerk_omschrijving").Value;
        var kdomein_key = oRsk("fac_kenmerkdomein_key").Value;
        var confidential= oRsk("kenmerk_systeem").Value&4;
        oRsk.Close();
        var vold_track = vold; // De waarde die bij tracking getoond wordt kan wijzigen tov de waare die in de tabel moet worden opgeslagen.
        var vnew_track = vnew; // Gebruik daarom voor de tracking tekst nieuwe variabelen!
        if (confidential)
        {
            vold_track = vnew_track = L("lcl_confidential_mask");
        }
        else
        {
            if (flex.type == "S" || flex.type == "r" || flex.type == "R")
            {
              if (vold != "")
              {
                var oRsd = Oracle.Execute("SELECT fac.getdomeinwaarde(" + kdomein_key + ", " + safe.quoted_sql(vold) + ") domeinwaarde FROM DUAL");
                vold_track = oRsd("domeinwaarde").Value;
                oRsd.Close();
              }

              if (vnew != "")
              {
                var oRsd = Oracle.Execute("SELECT fac.getdomeinwaarde(" + kdomein_key + ", " + safe.quoted_sql(vnew) + ") domeinwaarde FROM DUAL");
                vnew_track = oRsd("domeinwaarde").Value;
                oRsd.Close();
              }
            }
        }
        trackarray.push(label + ": " + buildTrackText("varchar", vold_track, vnew_track, {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 = " + kenmerk_key;
      oRs = Oracle.Execute(sql2);
      verbruiksmeter = oRs("ins_kenmerk_meetwaarde").Value == 1;
      oRs.Close();
    }

    // A real kenmerk value exists
    if (flex.value == "" || flex.value == "undefined")
    {
      if (kenmerk_keys[kenmerk_key])
      {
        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 = " + kenmerk_key
              + "   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 + " = " + kenmerk_key
              + "   AND " + params.kenmerkTable + "_VERWIJDER IS NULL";
          Oracle.Execute(sql);
        }
      }
      continue;
    }

    if (kenmerk_key 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 = " + kenmerk_key
              + " 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 + ", " + kenmerk_key + ")";
          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 + " = " + kenmerk_key
              + "   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
            + ", " + kenmerk_key
            + ", " + safe.quoted_sql(flex.value)
            + (params.moduleName? ", " + safe.quoted_sql(params.moduleVal): "")
            + ")";
        Oracle.Execute(sql);
      }
    }
  }

  if (params.module == "MLD" && blokkenmerk_key_arr.length)
  { // Als een melding is doorgestuurd kunnen er mld_kenmerkblokmelding van de oude stdmelding zijn die verwijderd moeten worden.
    mld.deleteOldFlexBlocks(parentKey, blokkenmerk_key_arr);
  }

  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, true);
    evaluateflexexpressions(module, parentKey, true);
    //removekenmerkwaarden(module, parentKey);
  }
  return trackarray;
}

function savekenmerkwaarden(module, refkey, kenmerkniveau, flexdata, savetodb)
{
  if (isEmptyObject(flexdata)) {
    return;
  }
  Oracle.Transaction(); // Véél sneller bij heel veel flexkenmerken
  for (var kenmerkkey in flexdata)
  {
    var expr = flexdata[kenmerkkey].expr || 0;
    if (expr == 2)
    {
        if (savetodb) // show-expressies met savetodb niet uitrekenen
            expr = 0; // DB37 slaat ze dan foutief ook op
        else
            expr = 1; // gewoon uitrekenen
    }
        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_kenmerklen"
                + "           , fac_kenmwaarden_kenmerkdec"
                + "           , 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].len ? flexdata[kenmerkkey].len : "NULL")
                + "           , " + (flexdata[kenmerkkey].dec ? flexdata[kenmerkkey].dec : "NULL")
                + "           , " + expr
                + "           , " + user_key
                + "            )";
    Oracle.Execute(sql);
  }
  Oracle.Commit();
}

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;
        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");
    var multiMode = params.multiMode;
    var multiEdit = params.multiEdit;
    var as_array = (params.returnArray ? params.returnArray : false);

    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;
    }

    var copyfin_key = getFParam("copyfin_key", -1);
    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 kp = getFParam(flex + "p", "");          // lengte veld (alleen voor type C en N)
        var kd = getFParam(flex + "d", "");          // aantal decimalen (alleen voor type N)
        var kmin = getFParam(flex + "min", "");      // Minimum waarde (alleen voor type N)
        var kmax = getFParam(flex + "max", "");      // Maximum waarde (alleen voor type N)
        var kv = getFParam(flex + "val", "");        // kenmerk waarde
        var ke = getFParamInt(flex + "e", 0);        // Flexkenmerk is een expressie.
        var kteam = getFParamInt(flex + "team", ""); // behandelteam (alleen voor type b)
        var kprs= getFParamInt(flex + "prs", "");    // behandelaar (alleen voor type b)

        // Kijken of er een checkbox "Gereed" in de (blok)header aanwezig is.
        var kcomplete = 0;                           // blok gereed (alleen voor type b)
        var isCheckbox = getFParamInt("has_" + flex + "complete", 0) == 1;
        if (isCheckbox)
        {
          var isChecked = Request.Form(flex + "complete").Count;
          kcomplete = (isChecked ? 1 : 0);
        }

        var checkVal = "check_" + flex + "val";
        if (multiEdit) {
            checkVal = "mcb_" + flex + "val";
        } else if (kt == "S" && getFParam("check_suggest" + flex, false)) // suggest with [check_suggest_k9] name
          checkVal = "check_suggest" + flex;

        // Uitzondering "ALG/R" is tijdelijke oplossing voor "FCLT#52926: Multi wijzigen van ruimtes werkt niet" zodat de kenmerken i.i.g. opgeslagen worden.
        // TODO voor 2018.1: "Multi wijzigen" m.b.v. vinkjes mogelijk maken.
        if ((multiMode || multiEdit) && getFParam(checkVal, "off") != "on" && kt != "F") {
          continue;
        }

        if (kt == "F" && kv == "on" && (copyfin_key > 0))
        {   // De filename wordt als label getoond achter een checkbox.
            // Filename moet worden gekopieerd, niet of het vinkje aan/uit staat.
            kv = getFParam(flex + "bval", "");
        }

        // Hier is iets mis (YKPN#53546 & YKPN#56907)
        // Er wordt (schijnbaar) als suggest_value een "x" meegegeven ipv een key
        // Even loggen en proberen te achterhalen hoe dit nou komt.
        if (kt == "S" && kv != "" && params.flexPath == "BEZ/B" && params.allKenmerkenSQL && isNaN(parseInt(kv, 10)))
        {
          var sql = "SELECT fac_kenmerkdomein_kolomnaam"
                  + "  FROM fac_kenmerkdomein, (" + params.allKenmerkenSQL + kk + ") bez_k"
                  + " WHERE fac_kenmerkdomein.fac_kenmerkdomein_key = bez_k.fac_kenmerkdomein_key"
                  + "   AND fac_kenmerkdomein_verwijder IS NULL"
                  + "   AND fac_kenmerkdomein_module = 'BEZ'";
          var oRs = Oracle.Execute(sql);
          if (String(oRs("fac_kenmerkdomein_kolomnaam").Value).match(/\_key$/i))
          {
            __DoLog("Ongeldige suggest waarde \"" + kv + "\" ingevuld voor flexveld met key " + kk);
            __DoLogForm();
            kv = "";
          }
          oRs.Close();
        }

        // Native datepicker
        if (kt == "DN")
        {
          kt = "D";
          // Change format from [YYYY-MM-DD] -> [DD-MM-YYYY] for Oracle
          // It's format is already checked by checkKenmerk()
          kv = kv.split("-").reverse().join("-");
        }

        // 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" || kt == "V")
        {
          // 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".
          }
          else
          {
            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.
            }
          }
        }

        // 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" || kt == "R") && kv == "-1") kv = ""; // anders zouden we '-1' opslaan

        var kkid = (params.multikenmerk && params.multikenmerk[kk] ? params.multikenmerk[kk].kenmerk_key : kk);
        jsondata[kk] = { id: kkid,
                         //name: "",
                         type: kt,
                         len: kp,
                         dec: kd,
                         min: kmin,
                         max: kmax,
                         value: kv,
                         expr: ke,
                         team: kteam,
                         prs: kprs,
                         complete: kcomplete,
                         kfieldid: flex
                       };
      }
    }

    var json_result = (as_array ? [] : jsondata);
    if (as_array)
    {   // Converteer object naar array.
        for (kenmerkkey in jsondata)
        {
            json_result.push(jsondata[kenmerkkey]);
        }
    }

    return json_result;
}

function getMultiRefKenmerken()
{   // Haal de referentie kenmerken op bij de actie multi kenmerk wijzigen.
    // Dit zijn de kenmerken van een van de meldingen/contracten/etc. die voor alle geselecteerde meldingen/contracten/etc. geldig zijn.
    // Hier worden later de kenmerken van de overige meldingen/contracten/etc. bij gezocht.
    var nameprefix = getFParamSafe("nameprefix");
    var kenmerk_count = getFParamInt("k_all", 0);
    var kkey_arr = [];
    for (k=0; k<=kenmerk_count; k++)
    {
        var mcb_name = "mcb_" + nameprefix + k + "val";
        if (getFParamSafe(mcb_name, "off") == "on")
        {
            var ref_kenmerk_key = getFParamInt(nameprefix + k + "key");
            kkey_arr.push(ref_kenmerk_key);
        }
    }
    return kkey_arr;
}

function getObjectsCommonProperties(disc_arr)
{   // Bepaal welke kenmerksoorten de stdmeldingen/contractsoorten/etc. de geselecteerde meldingen/contracten/etc.
    // gemeenschappelijk hebben.
    if (disc_arr.length > 0)
    {   // Voor elke contractsoort kijken of het kenmerk in referentie-kenmerk-array ook bij de contractsoort voor komt.
        // Gebruik de eerste contractsoort voor de kenmerken waartegen je de overige contractsoorten gaat controleren.
        // Zit een kenmerk uit de eerste contractsoort niet in een andere contractsoort dan kan het kenmerk verwijderd worden.
        for (var m=1; m<disc_arr.length; m++)
        {
            var v_ref = disc_arr[0];
            var v_std = disc_arr[m];
            for (r=v_ref.lvlkenmerk_list.length-1; r>=0; r--)  // Ja, klopt: reverse loop. Elementen uit array moet je achteraan verwijderen ivm index.
            {
                var v_refknm = v_ref.lvlkenmerk_list[r];
                var found = false;
                for (var k=0; k<v_std.lvlkenmerk_list.length; k++)
                {
                    var v_stdknm = v_std.lvlkenmerk_list[k];
                    if (v_stdknm.srt_key == v_refknm.srt_key && v_stdknm.km_name == v_refknm.km_name)
                    {
                        found = true;
                    }
                }
                if (!found)
                {
                    v_ref.lvlkenmerk_list.splice(r,1);
                }
            }
        }
    }
    return disc_arr;
}

%>