Metakit@web

 

par Miko


Comme vous le savez, j'aime bien jouer avec les bases de données, et je suis un maniaque de la traduction. La mode est aux applications Web, je vous fais donc cadeau de ma dernière traduction: "Metakit@Web" de Stefan Vogel.

La dernière version (originale) se trouve sur le wiki anglophone : http://wiki.tcl.tk/10241

Pour utiliser le programme, il vous faut le serveur Tclhttp et httpthread.tcl : http://sourceforge.net/tracker/index.php?func=detail&aid=635083&group_id=12884&atid=112884

Sans cette version il ne vous sera peut-être pas possible d'éxécuter les scripts présents dans le réperoire "custom" du serveur.

Par défaut le programme recherche les metakits dans /metakits (sous Windows, si vous avez installé le serveur sous d:\\tclhttpd3.4.2, ce sera d:\\metakits.) Ensuite, c'est biblique, yaka cliquer!!!

Voila qui remplacera avantageusement votre install de Java+Tomcat+Oracle et qui vous permettra de récupérer quelques gigaoctets sur votre disque et de nombreux cycles Cpu...


Le code :


    # ------------------------------------------------------------------------------
    # Metakit@Web -- Webadministration-interface for Metakit with Tclhttpd
    #
    # INSTALLATION:
    # * Simply copy this file to <Tclhttpd-path>/custom
    # * Adapt "aConfig(databaseDir)" to your "metakit"-directory
    # * Restart Tclhttpd and go to "http://your.domain/mkweb"
    # * Because of security-reasons you should be sure that  nobody has
    #   access to your Tclhttpd :-)
    #
    # Stefan Vogel (stefan at vogel-nest dot de)
    #
    # TODOs:
    # * choose column to display
    # * further error-checking with javascript (according to datatype)?
    # * split functionality from html
    # * use oomk
    # * make a starkit
    # * check performance
    # * set initial focus to input-fields
    # * document functions and file with standard doc-header
    # * make options session-aware (permanent cookie?)
    # * make editable row-numbers
    # * no sub-subviews
    # * caching in HTTP-Header
    #
    # HISTORY:
    #
    # 11/01/2003: Version 0.4 fr
    #  *traduction française Michel Salvagniac michel.salvagniac at free dot fr
    #
    # 10/28/2003: Version 0.4
    #  * restructured (more MVC-like ...)
    #  * subviews are now supported
    #  * moved configuration in extra-window
    #  * added gimmick: (un)mark-images (see metakit::execute/image.gif)
    #  * fixed several bugs and introduced lots of new ones
    #
    # 10/22/2003: Version 0.3
    #  * changed Httpd_Redirect to the correct way for redirects:
    #    return -code 302 ... (see eof metakit::web::execute)
    #  * fixed minor bug in AddCursor
    #
    # 10/22/2003: Version 0.2 (initial)
    #
    # by Stefan Vogel (stefan at vogel-nest dot de), see
    # http://mini.net/tcl/10241
    #
    # ------------------------------------------------------------------------------

    package require Mk4tcl
    package require html

    namespace eval  metakit::web {
        variable aConfig
        array set aConfig {
            version 0.4
            prefix mkweb_fr
            databaseDir /metakits
            maxRows {20}
            maxSubRows {4}
            maxChars {250}
            rowColsTexts {10 20}
            displayBinary 0
            subviewsEmbedded 1
            title "Metakit@Web"
        }
        # just a mapping to display "S" as datatype even if that's default
        variable aDatatypeMap
        array set aDatatypeMap {"" S I I F F L L B B M M D D}
        ::html::init [list input.size 15]
        ::html::headTag [subst {link rel="stylesheet" href="/$aConfig(prefix)/mkweb.css"}]
        ::html::headTag [subst {script language="JavaScript" src="/$aConfig(prefix)/mkweb.js" type="text/javascript"></script}]
    }

    # plug the given path to Metakit@Web into Tclhttpd
    Direct_Url /$metakit::web::aConfig(prefix) metakit::web::execute

    # the following links exist:
    # <prefix>/help         - display help
    # <prefix>/mkweb.css    - deliver stylesheet
    # <prefix>/mkweb.js     - deliver javascript
    # <prefix>/image.gif    - display image
    # <prefix>/download.bin - download-link for binary columns
    # <prefix>/saverow      - posted to, to save an edited row
    #
    # all other go directly to proc "execute" with parameter "cmd"
    # execute acts a bit like a "controller".

    # Main-method, this acts a little bit like a controller-template
    # given a "cmd" it dispatches to the "action"
    proc metakit::web::execute {args} {
        variable aConfig
        array set params $args
        set cmd ""
        set redirect ""
        switch -- [Show params(cmd)] {

            db_create_new_ui {
                # show html-input-form to create a new database
                set cmd {GenerateHtml [ui_createNewDb]}
            }
            db_create_new {
                # action to really create the database
                set cmd {mk_createDb $params(filename)}
                set redirect [Href "" "" cmd db_list]
            }
            db_delete {
                # delete database, no html-form necessary
                set cmd {mk_deleteDb [file join $aConfig(databaseDir) $params(filename)]}
                set redirect [Href "" "" cmd db_list]
            }
            db_list {
                # show overview over all file in databaseDir
                set cmd {GenerateHtml [ui_listDb $aConfig(databaseDir)]}
            }

            views_list {
                # show overview of all views in selected metakit
                if {[Show params(db)] == ""} {
                    # initial case when called with "nothing"
                    set cmd {GenerateHtml ""}
                } else {
                    set cmd {GenerateHtml [ui_listViews $params(db)]}
                }
            }
            view_create_ui {
                # html-input-form to create a new view
                set cmd {GenerateHtml [ui_createView $params(db)]}
            }
            view_create {
                # action to really create view, redirect to the list of all views
                set cmd {mk_createView $params(db) $params(view) $params(structure)}
                set redirect [Href "" "" cmd views_list db $params(db)]
            }
            view_modify_ui {
                set cmd {GenerateHtml [ui_modifyView $params(db) $params(view)]}
            }
            view_modify {
                set cmd {mk_modifyView $params(db) $params(view) $params(structure)}
                set redirect [Href "" "" cmd views_list db $params(db)]
            }
            view_delete {
                set cmd {mk_deleteView $params(db) $params(view)}
                set redirect [Href "" "" cmd views_list db $params(db)]
            }

            content_browse {
                set cmd {GenerateHtml [ui_browseContent $params(db) $params(view) $args]}
            }
            content_new_ui {
                set cmd {GenerateHtml [ui_newContent $params(db) $params(path) $args]}
            }
            content_edit_ui {
                set cmd {GenerateHtml [ui_editContent $params(db) $params(path) $args]}
            }
            content_delete {
                # maybe marked multiple elements to delete (via checkboxes), all beginning with delete_col_...
                set lPath [Show params(delete_col)]
                foreach elem [array names params delete_col_*] {
                    lappend lPath [string range $elem 11 end]
                }
                set cmd {mk_deleteRow $params(db) $lPath}
                array unset params delete_col*
                set redirect [Href "" [array get params] cmd content_browse]
            }
            content_browse_subview {
                set cmd {GenerateHtml [ui_browseSubViewContentWrap $params(db) $params(view) $params(index) $params(subview) $args]}
            }

            "" {
                # initial call, return frameset
                set cmd {GetFrameset $aConfig(prefix)}
            }
            default {
                error "No such cmd '[Show params(cmd)]'!"
            }
        }
        set result [eval $cmd]
        if {$redirect != ""} {
            return -code 302 $redirect
        }
        return $result
    }

    ################################################################################
    # Direct-Urls for different-purposes
    # All direct-urls listed here are not dispatched from the controller-proc "execute"
    # but instead called directly from someplaces

    # issued from edit/save or new row/create
    # not called via controller because the enctype is multipart/form-data
    # and therefore the args look a little bit different
    proc metakit::web::execute/saverow {args} {
        # decoding isn't done in multipart/form-data
        foreach {n v} [ncgi::nvlist] {
            set _args($n) [lindex $v 1]
        }
        mk_openDb [set db $_args(db)]

        if {[mk_hasIndex $_args(path)]} {
            # update-operation, action from clicking "save" in edit-form (update)
            foreach {key value} [array get _args colvalue_*] {
                set key [string range $key 9 end]
                if {[Show _args(bool_colvalue_$key)] != 1} {
                    lappend lRow $key $value
                }
            }
            eval mk::set $_args(path) $lRow
        } else {
            # insert operation
            foreach {key value} [array get _args colvalue_*] {
                lappend lRow [string range $key 9 end] $value
            }
            eval mk::row append $_args(path) $lRow
        }
        mk::file commit db
        mk_closeDb $db
        # delete unnecessary (save-specific) parameters from array
        array unset _args bool_colvalue_*
        array unset _args colvalue_*
        array unset _args path
        # redirect to content-display-page
        return -code 302 [Href "" [array get _args] cmd content_browse]
    }

    # get data from binary-columns
    # needs parameters: db, path, column
    proc metakit::web::execute/download.bin {args} {
        array set _args $args
        set ::metakit::web::execute/download.bin application/octet-stream
        mk_openDb $_args(db)
        set result [mk::get $_args(path) $_args(column)]
        mk_closeDb db
        return $result
    }

    # Configuration-dialog
    proc metakit::web::execute/config {args} {
        variable aConfig
        array set _args $args
        if {[Show _args(mode)] == "save"} {
            # set retrieved values into config-array and go back
            foreach var {maxRows maxSubRows maxChars displayBinaries subviewsEmbedded} {
                set aConfig($var) [Show _args($var)]
            }
            set aConfig(rowColsTexts) [list $_args(rowsText) $_args(colsText)]
            # return "pop-down"-statement
            return {<html><head><script type="text/javascript">window.close();</script></head><body></body></html>}
        } else {
            # browse-mode
            return [GenerateHtml [subst {<h1>Configuration</h1>
                <form action="[Href /config ""]" method="get">
                [GenerateHiddenFields mode save]
                <table>
                <tr><td>Nombre de lignes des zones de texte editables:</td><td><input type="text" name="rowsText" value="[lindex $aConfig(rowColsTexts) 0]" size="2"></td></tr>
                <tr><td>Nombre de colonnes des zones de texte editables:</td><td><input type="text" name="colsText" value="[lindex $aConfig(rowColsTexts) 1]" size="2"></td></tr>
                <tr><td>Afficher Binaires?</td><td><input type="checkbox" name="displayBinaries" value="1"[expr {[Show aConfig(displayBinaries)] == 1 ? " checked" : ""}]></td></tr>
                <tr><td>Lignes maximum affichées pour la vue:<br>
                (laisser vide pour tout afficher)</td><td><input type="text" name="maxRows" value="$aConfig(maxRows)" size="2"></td></tr>
                <tr><td>Lignes maximum affichées pour la sous-vue:<br>
                (laisser vide pour tout afficher)</td><td><input type="text" name="maxSubRows" value="$aConfig(maxSubRows)" size="2"></td></tr>
                <tr><td>Nombre de caractères affichés dans une cellule:<br>
                (laisser vide pour tout afficher)</td><td><input type="text" name="maxChars" value="$aConfig(maxChars)" size="2"></td></tr>
                <tr><td>Montrer les sous-vues embarquées?</td><td><input type="checkbox" name="subviewsEmbedded" value="1"[expr {[Show aConfig(subviewsEmbedded)] == 1 ? " checked" : ""}]></td></tr>
                <tr><td colspan="2" align="middle"><input type="submit" class="button" value="Enregistrer">&nbsp;<input type="button" class="button" value="Annuler" onclick="window.close();"></td></tr>
                </table>
                </form>}]]
        }
    }

    ################################################################################
    # Help - to use this from other functions args contains the "keyword" for help
    #        as first parameter. If no keyword is given, display the whole page
    #        (usually when starting mkweb)
    proc metakit::web::execute/help {args} {
        variable aConfig
        switch -- [lindex $args 0] {
            search {
                set result [subst {Ex -glob name "Hello w*"<br>
                    [::html::tableFromList {
                        "<em>prop value</em>" "Insensible à la casse"
                        "<em>-min prop value</em>" "Propriété supérieure ou égale à la valeur (casse ignorée)"
                        "<em>-max propvalue</em>"  "Propriété inférieure ou égale à la valeur (casse ignorée)"
                        "<em>-exact prop value</em>" "Correspondance exacte"
                        "<em>-glob prop pattern</em>" "Correspondance 'glob-style' expression wildcard"
                        "<em>-globnc prop pattern</em>" "Correspondance 'glob-style', casse ignorée"
                        "<em>-regexp prop pattern</em>" "Correspondance à l'expression regulière indiquée"
                        "<em>-keyword prop word</em>" "Correspondance du mot comme texte libre ou préfixe partiel"
                    }]}]
            }
            columns {
                set result {Structure: e.g. <code>id:I name:S salary:F</code><br>
                    where: <table><tr><td>:S</td><td>string</td><td></td><td>:F</td><td>float</td></tr>
                    <tr><td>:I</td><td>integer</td><td></td><td>:D</td><td>double</td></tr>
                    <tr><td>:L</td><td>long</td><td></td><td>:B</td><td>binary</td></tr></table>}
            }
            default {
                set result [subst {<center><h1>Bienvenue au Metakit@Web (Version $aConfig(version))</h1></center>
                    <p>Juste pour me familiariser avec le <a href="http://www.equi4.com/metakit.html" target="_blank">Metakit</a> ,j'ai recherché un bon "viewer".
                    Malheureusement tous ceux que j'ai essayés soit ne fonctionnaient pas, soit ne faisaient pas ce que je voulais.</p>
                    <p>C'est pourquoi j'ai decidé d'écrire une interface web (de nos jours on en voit pour presque tout, ex. MySql). Comme l'interface devait être le plus facile possible,
                    j'ai choisi Tcl et son <a href="http://www.tcl.tk/software/tclhttpd/" target="_blank">Serveur Web (Tclhttpd)</a>
                    pour cet usage.</p>
                    <p>Et que dire? Tclhttpd est le serveur Web le plus cool que j'aie jamais vu.</p>
                    Les fonctionnalités du Metakit@Web sont:
                    <ul><li>installation facile dans Tclhttpd (un seul fichier)</li>
                    <li>Edition, et manipulation facile des fichiers Metakit (utile pour un prototypage rapide)</li>
                    <li>Navigation facile dans le contenu des fichiers Metakit</li>
                    </ul>
                    Les fonctionnalités manquantes (ATTENTION!!):
                    <ul><li>Pas d'autorisation, tout le monde peut modifier ou effacer les fichiers Metakit (cet outil est censé n'être
                    utilisé que par vous, sur votre machine)</li>
                    <li>Mono-utilisateur (pas de sessions)</li>
                    <li>confirmation Javascript seule pour l'effacement. Le site ne doit pas être robotisé, car si un d'entre eux
                    suit un lien "Effacer"... boum!!</li>
                    </ul>
                    <h2>Installation/Configuration</h2>
                    <p>Si vous êtes capable de lire ceci, vous avez installé Metakit@Web avec succès.<br>
                    Juste pour mentionner quelques points:</p>
                    <p>Vous devez créer un répertoire où vous placerez vos fichiers Metakit à editer avec
                    Metakit@Web. Ce répertoire est configuré dans le fichier <code>mkweb.tcl</code>
                    (Variable: <code>aConfig(databaseDir)</code> actuellement: <code>$aConfig(databaseDir)</code>).</p>
                    <p>Vous pourrez ensuite paramétrer <code>aConfig(prefix)</code> (currently: <code>$aConfig(prefix)</code>).
                    Cette variable détermine le préfixe de l'url avec laquelle vous accedez au Metakit@Web, actuellement:<br>
                    <a href="http://$::env(HTTP_HOST)/$aConfig(prefix).http://$::env(HTTP_HOST)/$aConfig(prefix)">http://$::env(HTTP_HOST)/$aConfig(prefix)</a>.</p>
                    <p>Vous pouvez aussi configurer certaines valeurs via le lien dans la frame de gauche. Saisissez simplement une valeur différente
                    et cliquez sur "Enregistrer". Ensuite vous pouvez "recharger" les pages dans lesquelles vous attendez les changements.</p>
                    <p><a href="#" onclick="window.open('[Href /config ""]', 'Configuration', 'dependent=yes,resizable=yes,width=450,height=300'); return false;">Configuration-options</a> (voir la fenêtre popup suivant le lien dans la "DB-frame") sont:
                    [::html::tableFromList {
                        {Combien de lignes/colonnes pour les zones de texte éditables}
                        {La taille de la zone de texte (:S - valeurs chaîne). Si vous stockez de grandes chaînes il peut être judicieux d'augmenter cette valeur. Si le rendu de la page est trop long, ce peut être une bonne idée de réduire cette valeur.}
                        {Afficher les Binaires?}
                        {Je ne sait pas ce que vous stockez dans une colonne binaire. Parfois, je l'utilise pour stocker de grands fichiers texte. Cochez la case, pour l'afficher en tant que texte.}
                        {Lignes maximum affichées par vue}
                        {Combien de lignes d'une vue seront affichées? Si vous le positionnez à "" toutes les lignes seront affichées (bonne idée seuluement pour les petites vues). Gardez à l'esprit que c'est une interface Web.}
                        {Lignes maximum affichées par sous-vue}
                        {Les sous-vues sont affichées incrustées dans leur vue. Donc quand vous avez de grandes sous-vues vous souhaiterez réduire ces valeurs.}
                        {Nombre de caractères affichés par cellule}
                        {Quand vous lisez le contenu d'une vue, toutes les valeurs chaîne seront tronquées à la taille indiquée ici (... est ajouté, si la chaîne est tronquée). Si vous voulez voir la totalité, donnez lui cette valeur: "".}
                        {Afficher les sous-vues?}
                        {J'aime voir les premières lignes des sous-vues incrustées dans leur vue. Si cela ne vous plait pas, décochez la case et vous verrez un lien sur la sous-vue à la place de ses données.}}]
                    <h2>Remarques Générales</h2>
                    <p>Les frames du Metakit@Web ressemblent à ceci:
                    <table border="1" width="100%">
                    <tr><td><em>La frame Fichier/base </em><br>Elle liste tous les fichiers de <code>aConfig(databaseDir)</code>. n'utilisez que des metakits ici. Effacer une base effacer le fichier physiquement!<br>
                    Vous trouverez égalemnt le lien sur la fenêtre de configuration et le lien sur l'aide.</td>
                    <td rowspan="2"><em>La frame principale</em><br>
                    Cette frame affiche le contenu des vues (et l'aide).</td></tr>
                    <tr><td><em>La frame des vues</em><br>Si vous selectionnez ( par un clic) un fichier de la frame DB, les vues et leur structure seront affichées ici. vous pouvez modifier les vues.</td></tr>
                    </table></p>
                    <p>En général la navigation (précédent, rafraîchir, ..) est pour vous et votre navigateur,
                    donc vous ne trouverez pas de boutons "précédent" dans le Metakit@Web.<br>
                    Normalement les noms des bases, des vues, les index de lignes, ont un lien sur leur contenu.<br>
                    La dernière colonne de l'en-tête de la table (base, vues ou lignes) contient le lien "Nouveau ...". Cliquez dessus pour saisir une nouvelle ligne, vue, ...</p>
                    <p>Usuellement vous irez dans la  frame Fichier/base, choisirez un fichier, irez dans La frame des vues et selectionnerez une vue(le contenu
                    sera affiché dans la frame principale).
                    <h2>Remerciements</h2>
                    <p>A Jean-Claude Wippler pour son cool <a href="http://www.equi4.com/metakit.html" target="_blank">Metakit</a>
                    et sa motivation pour affiner cet outil. Merci à Stefan Finzel, Reinhard Max et W. Jeffrey Rankin pour les rapports de bugs, les patches et pour l'aide sur le fonctionnement de Tclhttpd.
                    Et merci à toute la communauté Tcl pour sa patience infinie et ses
                    commentaires judicieux.</p>
                    <p>Did I already say that Tclhttpd and Metakit are sooooo coool? :-) (oui... N.D.T.:))</p>
                    Vous pouvez trouver des informations sur ce script là <a href="Tcl'ers">http://wiki.tcl.tk/Stefan%20Vogel">Tcl'ers Wiki</a> ou le télécharger
                    de ma
                    <a href="http://www.vogel-nest.de/tcl" target="_blank">Homepage</a>.</p>
                    <pre>Ce script est un logiciel libre. Vous l'utilisez à vos risques et périls.
                    Ne me blamez pas si ça tourne mal.
                    Mais dites-moi si vous l'appréciez :-)
                    </pre>
                    <p>Les suggestions ou les corrections de bugs sont bienvenues, envoyez-moi un email.<br>
                    Stefan Vogel -- stefan at vogel-nest dot de</p>
                }]
                set result [GenerateHtml $result]
            }
        }
        return $result
    }

    ################################################################################
    # CSS and JS

    # deliver the styleshet for Metakit@Web
    proc metakit::web::execute/mkweb.css {} {
        set ::metakit::web::execute/mkweb.css text/css
        set result {
            body { font-family:Arial,sans-serif; font-size:10pt; }
            h1 { font-size:14pt; color:#000080; }
            h2 { font-size:12pt; color:#000080; }
            td { font-family:Arial,sans-serif; font-size:10pt; vertical-align:top; }
            th { font-family:Arial,sans-serif; font-size:10pt; vertical-align:top; }
            .dml td,.cnt td { border:1px solid black; }
            .dml th { border:1px solid black; }
            .cnt th { border:1px solid black; background-color:#a4a4ff; color:#ffffff; }
            .subviewcnt th { border:1px solid black; background-color:#c0c0ff; color:#ffffff; font-size:8pt; }
            .subviewcnt td { border:1px solid black; font-size:8pt; }
            .scnd { background-color:#d9d9ff; }
            .subfirst { background-color:#ffffff; font-size:8pt; }
            .subscnd { background-color:#e8e8ff; font-size:8pt; }
            a.button { background-color:#a4a4ff; color:#ffffff; border:1px solid black; font-weight:bold; text-decoration:none; padding-left:3px; padding-right:3px; }
            .button { background-color:#a4a4ff; color:#ffffff; border:1px solid black; font-weight:bold; }
        }
        return $result
    }

    proc metakit::web::execute/mkweb.js {} {
        set ::metakit::web::execute/mkweb.js application/x-javascript
        set result {
            function linkClick(msg) {
                return confirm(msg);
            }
            function setChecks(pattern, check) {
                var form = document.browse;
                var pat = eval("/^delete_col_" +pattern+ "![0-9]+$/");
                for (var c = 0; c < form.elements.length; c++)
                if (form.elements[c].type == 'checkbox' && pat.test(form.elements[c].name))
                form.elements[c].checked = check;
            }
        }
        return $result
    }

    proc metakit::web::execute/image.gif {args} {
        # set f [open gif r]
        # fconfigure $f -translation binary
        # set r [read $f]
        # close $f
        # binary scan $r H* result
        set ::metakit::web::execute/image.gif image/gif
        array set _args $args
        set result "474946383961"
        switch -- $_args(name) {
            mark -
            unmark {
                append result "0f000f00f70000050505fbfbfb[string repeat 01 759]\
                        00000021f904010000ff002c000000000f000f004008"
                if {$_args(name) == "mark"} {
                    append result "4300ff091c48b0e03f00000e22443890a14283071f2a5c4891a2c08a181d324c7871234787110982\
                            84083123c68e24258e44a931a1c78f303b724429b1244c930bff0504003b"
                } else {
                    append result "3b00ff091c48b0e03f00000c0e442890a1c2830921229c48d161c58b16232accf81062c787182f36\
                            d4689023c8880e37a22459d0a4ca912127fe0b08003b"
                }
            }
            pixel {
                if {[info exists _args(color)]} {
                    append result "01000100910000$_args(color)0000000000000000002c00000000010001000008040001040400"
                } else {
                    # transparentes pixel
                    append result "02000200800000ffffff00000021f90401000000002c000000000200020040020284510035"
                }
            }
        }
        return [binary format H* [string map {" " ""} $result]]
    }

    ################################################################################
    # html-metakit-specific-functions (prefix "ui_")
    # Approx. the "VIEW"-part of MVC

    proc metakit::web::ui_createNewDb {} {
        return [subst {[::html::h1 "Créer le metakit"]
            <form action="[Href "" ""]" method="get">
            [GenerateHiddenFields cmd db_create_new]
            [::html::textInputRow "Nom du fichier:" filename]
            <input type="submit" class="button" value="Créer">
            </form>}]
    }

    proc metakit::web::ui_listDb {dbDir} {
        variable aConfig
        set result [subst {<tr><th>Nom du fichier</th><th>Taille</th><th><a href="[Href "" "" cmd db_create_new_ui]" class="button">Nouveau</a></th></tr>}]
        if {![file isdirectory $aConfig(databaseDir)]} {
            error "<h1>Erreur</h1>Le répertoire de la base de données '$aConfig(databaseDir)' n'existe pas: . Créez-le ou modifiez <code>mkweb.tcl metakit::web::execute::aConfig(databaseDir)</code>."
        } else {
            append result [::html::foreach db [glob -nocomplain -- $dbDir/*] {<tr><td><a href="[Href "" "" cmd views_list db [set _db [file tail $db]]]" target="views">$_db</a></td><td align="right">[format "%.2f kB" [expr [file size $db]/1024.0]]</td>
                <td><a href="[Href "" "" cmd db_delete filename $_db]" class="button" onclick="return linkClick('Effacer completement le metakit: $_db?');">Effacer</a></td></tr>}]
            return [subst {[::html::h1 "Fichiers"]
                <table width="100%">$result</table>
                <div align="right"><a href="#" onclick="window.open('[Href /config ""]', 'Configuration', 'dependent=yes,resizable=yes,width=450,height=300'); return false;">Configurer $aConfig(title)</a><br>
                <a href="[Href /help ""]" target="main">Aide</a></div>}]
        }
    }

    # this is unfortunately a little mix of logic and html
    proc metakit::web::ui_listViews {db} {
        variable aDatatypeMap
        mk_openDb $db
        set result [::html::h1 "Vues du metakit: '$db'"]
        append result [subst {<table class="dml"><tr><th>Vue</th><th>Colonnes</th><th>Type</th><th colspan="2"><a href="[Href "" "" cmd view_create_ui db $db]" class="button">Nouvelle&nbsp;Vue</a></th></tr>}]
        foreach view [lsort [mk::file views db]] {
            append result [subst {<tr><td><a href="[Href "" "" cmd content_browse db $db view $view]" target="main">$view</a></td><td></td><td></td>
                <td><a href="[Href "" "" cmd view_modify_ui db $db view $view]" class="button">Modifier</a></td>
                <td><a href="[Href "" "" cmd view_delete db $db view $view]" class="button" onclick="return linkClick('Really Effacer complete view: $view?');">Effacer</a></tr>
            }]
            foreach col [mk::view layout db.$view] {
                if {[llength $col] > 1} {
                    # we have a subview here
                    set lColName [lindex $col 0]
                    set lDatatype "Subview"
                    foreach col [lindex $col 1] {
                        foreach {colName datatype} [split $col :] {break}
                        lappend lColName "&nbsp;&gt;&nbsp;$colName"
                        lappend lDatatype "&nbsp;&gt;&nbsp;$aDatatypeMap($datatype)"
                    }
                    append result [subst {<tr><td></td><td><em>[join $lColName <br>]</em></td>
                        <td><em>[join $lDatatype <br>]</em></td><td></td><td></td></tr>
                    }]
                } else {
                    foreach {colName datatype} [split $col :] {break}
                    append result [subst {<tr><td></td><td>$colName</td>
                        <td>$aDatatypeMap($datatype)</td><td></td><td></td></tr>
                    }]
                }
            }
            append result {<tr><td colspan="5"></td></tr>}
        }
        append result {</table>}
        mk_closeDb db
        return $result
    }

    proc metakit::web::ui_createView {db} {
        set result [subst {<h1>Nouvelle vue pour le metakit: '$db'</h1>
            <form action="[Href "" ""]" method="get">
            [GenerateHiddenFields "" db $db cmd view_create]
            Nom de la vue:<br><input type="text" name="view" size="40"><br>
            Structure:<br><input type="text" name="structure" size="40"><br>
            <input type="submit" class="button" value="Créer"><br>
            [execute/help columns]</form>}]
    }

    proc metakit::web::ui_modifyView {db viewname} {
        mk_openDb $db
        set result [subst {<h1>Modifier le metakit: '$db' vue: '$viewname'</h1>
            <form action="[Href "" ""]" method="get">
            [GenerateHiddenFields "" db $db view $viewname cmd view_create]
            Structure:<br>
            <input type="text" name="structure" value="[mk::view layout db.$viewname]" size="40"><br>
            <input type="submit" class="button" name="mode" value="Modifier"><br>
            <b>Modifier la structure peut effacer tout le contenu!</b><br>[execute/help columns]
            </form>}]
        mk_closeDb $db
        return $result
    }

    # the following parameters are used to browse through content
    # first - startindex
    # count - number of rows to show
    # sort  - either sort or rsort
    # column - sort - column
    # query - user queries
    proc metakit::web::ui_browseContent {db viewname paramList} {
        variable aConfig
        # set cmd to content_browse, no matter what
        array set _args [set p [concat $paramList cmd content_browse]]

        mk_openDb $db
        set view [mk::view open db.$viewname]
        # determine the datatypes for each column and store it
        set lColumns [mk_columnDatatypes $db $viewname aDatatype]
        set cursor ""

        # attention, e.g for wikit-database we need to escape some html-chars
        set cmd "mk::select db.$viewname"

        # build the metakit-select command from HTTP-query-parameters:
        if {[Show _args(first)] != ""} {
            append cmd " -first $_args(first)"
        }
        if {[Show _args(sort)] != "" && [Show _args(column)] != ""} {
            append cmd " -$_args(sort) $_args(column)"
        }
        if {[Show aConfig(maxRows)] != ""} {
            append cmd " -count $aConfig(maxRows)"
        }
        append cmd " [Show _args(query)]"

        set sContent [subst {<tr><th rowspan="2"><a href="#" onclick="setChecks('db.$viewname',false); return false;"><img src="[Href /image.gif "" name unmark]" border="0" width="15" height="15" alt="Unmark all"></a><a href="#" onclick="setChecks('db.$viewname',true); return false;"><img src="[Href /image.gif "" name mark]" border="0" width="15" height="15" alt="Mark all"></a></th><th rowspan="2">index <a href="[Href "" $paramList sort "" column ""]">&uarr;</a></th>}]
        set secondHead ""
        foreach col $lColumns {
            if {$aDatatype($col) == "Subview"} {
                # this is a subview
                append sContent [subst {<th colspan="[llength $aDatatype(:$col)]">$col&nbsp;<em>(Subview)</em></th>}]
                append secondHead "<th>[join $aDatatype(:$col) </th><th>]</th>"

            } else {
                append sContent [subst {<th rowspan="2">${col}:$aDatatype($col) <a href="[Href "" $paramList column $col sort sort]">&uarr;</a>&nbsp;<a href="[Href "" $paramList column $col sort rsort]">&darr;</a></th>}]
            }
        }
        append sContent [subst {<th rowspan="2"><a href="[Href "" $paramList cmd content_new_ui path db.$viewname]" class="button">Nouvelle&nbsp;ligne</a></th></tr>
            <tr>$secondHead</tr>}]

        set i 0
        foreach index [eval $cmd] {

            append sContent [subst {<tr[expr {[incr i]%2 ? "":" class=\"scnd\""}]><td><input type="checkbox" name="delete_col_db.$viewname!$index" value="1"></td><td><a href="[Href "" $paramList cmd content_edit_ui path db.$viewname!$index]">$index</a></td>}]
            # store the data of this row temporarily into an array
            array unset data
            array set data [mk::get db.$viewname!$index]
            foreach col $lColumns {
                if {$aDatatype($col) == "Subview"} {
                    # include subview subview
                    append sContent [subst {<td colspan="[llength $aDatatype(:$col)]">
                        [ui_browseSubViewContent $db $viewname $index $col aDatatype 1 $paramList]</td>}]
                } else {
                    append sContent [subst {<td>[PrepareData $data($col) $aDatatype($col) [Href "/download.bin" "" db $db path db.$viewname!$index column $col]]&nbsp;</td>}]
                }
            }
            append sContent [subst {<td><a href="[Href "" $paramList cmd content_delete delete_col db.$viewname!$index]" class="button" onclick="return linkClick('Really Effacer row: $index?');">Effacer</a></tr>\n}]
        }
        # only add a cursor (0-19 20-...) if necessary, that is if there is a "block"-limit (maxRows)
        # and the number of selected rows is larger than the block-size
        if {[Show aConfig(maxRows)] != ""} {
            set cursor <p>[AddCursor [Show _args(first)] $aConfig(maxRows) [$view size] "[Href "" $paramList]"]</p>
        }
        set result [subst {[::html::h1 "Contenu du metakit: '$db' vue: '$viewname'"]
            Taille totale de la vue: [$view size] lignes<br>
            Requête: <em>$cmd</em><br>
            $cursor
            <form name="browse" action="[Href "" ""]" method="post">
            [GenerateHiddenFields "" db $db view $viewname cmd content_delete]
            <table class="cnt">$sContent</table><br>
            <input type="submit" class="button" value="Effacer marqué" onclick="return linkClick('Vraiment effacer les lignes marquées?');">
            </form>
            <p><form name="query" action="[Href "" ""]" method="get">
            [GenerateHiddenFields "" db $db view $viewname cmd content_browse]
            <input type="text" name="query" value="[::html::quoteFormValue [Show _args(query)]]" size="30">
            <input type="submit" class="button" name="mode" value="Requête">
            </form></p>
            [execute/help search]
            </form>}]
        mk_closeDb $db
        return $result
    }

    proc metakit::web::ui_browseSubViewContentWrap {db viewname index subview paramList} {
        mk_columnDatatypes $db $viewname aDatatype
        return [subst {<h1>Contenu du metakit: '$db' vue: '$viewname!$index.$subview'</h1>
            <form name="browse" action="[Href "" ""]" method="post">
            [GenerateHiddenFields "" db $db view $viewname cmd content_delete]
            [ui_browseSubViewContent $db $viewname $index $subview aDatatype 0 $paramList]
            <input type="submit" class="button" value="Effacer marqué" onclick="return linkClick('Really Effacer all marked rows?');">
            </form>}]
    }

    proc metakit::web::ui_browseSubViewContent {db viewname index subview datatype showEmbedded paramList} {
        upvar $datatype aDatatype
        variable aConfig

        set addViewComplete [subst {<a href="[Href "" $paramList cmd content_browse_subview db $db view $viewname index $index subview $subview]">Complete subview: ${viewname}!$index.$subview ...</a>}]
        if {[Show aConfig(subviewsEmbedded)] != 1
            && $showEmbedded} {
            # subviews shouldn't be displayed embedded in view and we are in the view-view (LOL)
            return $addViewComplete
        }

        # create header-row in subview-table
        set result [subst {<table class="subviewcnt"[expr {$showEmbedded ? " width=\"100%\"" : ""}]><tr><th><a href="#" onclick="setChecks('db.$viewname!$index.$subview',false); return false;"><img src="[Href /image.gif "" name unmark]" border="0" width="15" height="15" alt="Unmark all"></a><a href="#" onclick="setChecks('db.$viewname!$index.$subview',true); return false;"><img src="[Href /image.gif "" name mark]" border="0" width="15" height="15" alt="Mark all"></a></th><th>sub-index</th>
            [::html::foreach subcol $aDatatype(:$subview) {<th>${subcol}:$aDatatype($subview:$subcol)</th>}]
            <th><a href="[Href "" $paramList cmd content_new_ui path db.$viewname!$index.$subview]" class="button">Nouveau&nbsp;row</a></tr>
        }]
        set j 0
        set subcmd "mk::select db.$viewname!$index.$subview"
        if {[Show aConfig(maxSubRows)] != "" && $showEmbedded} {
            append subcmd " -count $aConfig(maxSubRows)"
        }
        # create one row in subview
        append result [::html::foreach sIndex [eval $subcmd] {
            <tr class="[expr {[incr j]%2 ? "subfirst":"subscnd"}]"><td><input type="checkbox" name="delete_col_db.$viewname!$index.$subview!$sIndex" value="1"></td><td><a href="[Href "" $paramList cmd content_edit_ui path db.$viewname!$index.$subview!$sIndex]">$sIndex</a></td>
            [::html::foreach {subkey subvalue} [mk::get db.$viewname!$index.$subview.$sIndex] {
                <td>[PrepareData $subvalue $aDatatype($subview:$subkey) [Href "/download.bin" "" db $db path db.$viewname!$index.$subview!$sIndex column $subkey]]&nbsp;</td>
            }]
            <td><a href="[Href "" $paramList cmd content_delete delete_col db.$viewname!$index.$subview.$sIndex]" class="button" onclick="return linkClick('Really Effacer subrow: $index.$sIndex?');">Effacer</td></tr>
        }]
        append result </table>
        if {$j == [Show aConfig(maxSubRows)] && $showEmbedded} {
            append result $addViewComplete
        }
        return $result
    }

    proc metakit::web::ui_createInputForm {db title lHeaderRow lDataRow paramList} {
        set result [subst {<h1>$title in metakit '$db'</h1>
            <form action="[Href /saverow ""]" method="post" enctype="multipart/form-data">
            [GenerateHiddenFields $paramList cmd content_save]
            <table class="cnt">
            [eval ::html::hdrRow $lHeaderRow]
            [eval ::html::row $lDataRow]
            </table><br>
            <input type="submit" class="button" value="Save">
            </form>}]
    }

    proc metakit::web::ui_newContent {db path paramList} {
        lappend paramList path $path
        set lDataRow {}
        foreach col [set lHeaderRow [mk_simpleDatatypes $db $path dummy]] {
            foreach {column datatype} [split $col :] {break}
            lappend lDataRow [PrepareInput $column $datatype]
        }
        return [ui_createInputForm $db "Nouvelle ligne dans la vue '$path'" $lHeaderRow $lDataRow $paramList]
    }

    #
    proc metakit::web::ui_editContent {db path paramList} {
        lappend paramList path $path
        set lDataRow {}
        set lHeaderRow [mk_simpleDatatypes $db $path aDatatype]
        foreach {col value} [mk::get $path] {
            lappend lDataRow [PrepareInput $col $aDatatype($col) $value]
        }
        return [ui_createInputForm $db "Edit row '$path'" $lHeaderRow $lDataRow $paramList]
    }

    ################################################################################
    # mk-specific-functions (prefix: "mk_")
    # The "Model"-part of MVC

    proc metakit::web::mk_openDb {filename} {
        variable aConfig
        catch {mk::file close db}
        set filename [file join $aConfig(databaseDir) $filename]
        if {![file exists $filename]} {
            error "No such file: '$filename'"
        }
        mk::file open db [file join $aConfig(databaseDir) $filename]
    }

    proc metakit::web::mk_closeDb {db} {
        catch {mk::file close $db}
    }

    proc metakit::web::mk_createDb {filename} {
        if {[info exists $filename] || $filename == ""} {
            error "Invalid or empty filename, could not create '$filename'!"
        }
        # simply open the file and close it
        mk::file open db $filename
        mk::file close db
    }

    # this is not really metakit-specific, but anyway
    proc metakit::web::mk_deleteDb {filename} {
        if {![file exists $filename]
            || ![file isfile $filename]} {
            error "Nom de fichier invalide '$filename'. Impossible de l'effacer!"
        }
        file delete -force $filename
    }

    proc metakit::web::mk_createView {db viewname structure} {
        mk_openDb $db
        mk::view layout db.$viewname $structure
        mk::file commit db
        mk_closeDb $db
    }

    proc metakit::web::mk_deleteView {db viewname} {
        mk_openDb $db
        mk::view delete db.$viewname
        mk::file commit db
        mk_closeDb $db
    }

    # assume db is already open
    proc metakit::web::mk_simpleDatatypes {db path aData} {
        upvar $aData _aData
        variable aDatatypeMap
        set lResult {}
        foreach col [mk::view info $path] {
            foreach {key datatype} [split $col :] {break}
            if {$datatype != "V"} {
                lappend lResult $key:$aDatatypeMap($datatype)
                set _aData($key) $aDatatypeMap($datatype)
            }
        }
        return $lResult
    }

    # returns 0 for view, 1 for subview
    # if there is a pattern like "!<number>." it must be a subview
    proc metakit::web::mk_isSubView {path} {
        # subview:  db.view!n.subview[!m],
        # view: db.view[!n]
        return [regexp -- {![0-9]+\.} $path]
    }

    # returns 0 if no index is in path (e.g db.view!n.subview)
    # used to distinguish between insert and update
    proc metakit::web::mk_hasIndex {path} {
        return [regexp -- {![0-9]+$} $path]
    }

    # assume db is already open
    proc metakit::web::mk_columnDatatypes {db viewname aData} {
        variable aDatatypeMap
        upvar $aData _aData
        set lColumns {}
        foreach col [mk::view layout db.$viewname] {
            if {[llength $col] > 1} {
                # subview
                set subviewname [lindex $col 0]
                set _aData($subviewname) Subview
                # store the columns of this subview
                set _aData(:$subviewname) {}
                lappend lColumns $subviewname
                foreach col [lindex $col 1] {
                    set col [split $col :]
                    lappend _aData(:$subviewname) [lindex $col 0]
                    # use a colon to split subview-name from subview-column
                    set _aData(${subviewname}:[lindex $col 0]) $aDatatypeMap([lindex $col 1])
                }
            } else {
                set col [split $col :]
                set _aData([lindex $col 0]) $aDatatypeMap([lindex $col 1])
                lappend lColumns [lindex $col 0]
            }
        }
        return $lColumns
    }

    proc metakit::web::mk_deleteRow {db lPath} {
        mk_openDb $db
        # catch the delete, maybe we have deleted a row and
        # afterwards we have path for subviews (which then cannot be deleted anymore, of course)
        # sort the path before deleting, because if <view>!5 is deleted before <view>!7
        # deletion of <view>!7 will fail. dictionary-sorting avoids this (it starts with
        # the highest numbers)
        foreach path [lsort -dictionary -decreasing $lPath] {
            catch {mk::row delete $path}
        }
        mk_closeDb $db
    }

    #################################################################################
    # some little Helperfunctions used throughout mkweb
    # starting with "upper-case"-first-character

    proc metakit::web::PrepareData {text datatype link} {
        variable aConfig
        set result $text
        switch -- [string tolower $datatype] {
            b {
                if {[Show aConfig(displayBinaries)] != 1
                    && [string length $text]} {
                    set result {<em>not displayed</em>}
                }
            }
            default {
                # avoid html-confusion
                set result [string map {< &lt;} $result]
            }
        }
        if {[Show aConfig(maxChars)] != ""
            && [string length $result] > $aConfig(maxChars)} {
            set result [string range $result 0 $aConfig(maxChars)]...
        }
        if {[string tolower $datatype] == "b" && [string length $text]} {
            # append download-link for binaries
            append result [subst {<br><a href="$link" target="_blank">Download</a>}]
        }
        return $result
    }

    proc metakit::web::PrepareInput {col datatype {value ""}} {
        variable aConfig
   JL