jWebTools

Documentation
Login

Using jWebTools

Introduction

jWebTools is a Tcl extension that provides set of software tools for the construction of websites.

The main tools are TclOO classes. One way to use jWebTools is instantiating classes that represent HTML tags (and as well other useful items). This makes it straightforward to produce highly structured code that reliably produce HTML output with static or dynamic content.

jWebTools also makes available the Tag namespace which allows creating pages without having to use TclOO features. Employing this variation of jWebTools is described below.

Installing jWebTools

Installation is straightforward. Download the archive from: https://thinairarts.com/fossil/jwebtools-src/download.

Once the archive is downloaded and decompressed, you'll have directory jwebtools-src with a jWebTools subdirectory. Copy the jwebtools directory to the usual location for extensions. Generally that's will be something like: /path/to/Tcl/lib/jwebtools.

Note that jWebTools has a few dependencies. First it uses new features of Tcl8.7 or 9.0. jWebTools uses the sqlite3 and tcllib extensions. Currently installing these on 9.0 is possible but both sqlite3 and tcllib need modifications to run on 9.0 so using 8.7 is therefore recommended.

Once installed, use jWebTools by putting the usual declaration in Tcl source files: package require jwebtools.

HTML tags

Currently (as of v1.0.6-5.1) jWebTools classes include: A Area B Body Button Code Div Em Figure Figcaption Footer Form H1 H2 H3 H4 H5 H6 Header Img Input Label Li Map Ol P Pre Span Textarea U Ul. Additional tags are easily added either by inserting a new class name to the lists in ooBody.tcl, or by subclassing Element or an existing tag class.

Using a tag class is simple:

[H1 new -x {My New Heading}]

This statement produces: <h1>My New Heading</h1>

HTML tag syntax

Tag classes are derived from the abstract class Element and take the same arguments:

[TAG new -attrs {ATTRIBUTE-LIST} -elements {ELEMENT-LIST} -txt {TEXT}]

Depending on the tag, some arguments may be omitted. Note that -elements and -txt are mutually exclusive.

A number of convenience features are available and widely used in jWebTools. These include:

-attrs or -As, -as
-elements or -Es, -E, -es, -e
-txt or -X, -x
-- or -> {ATTRS} {ELEMENTS} 

The last short form is used like this:

[Div new -> {class mydiv} [subst {
    [P new -x {My paragraph ...}]
}]]

Instead of:

[Div new -As {class mydiv} -Es [subst {
    [P new -x {My paragraph ...}]
}]]

As the short form is somewhat more compact and readable. In addition a further shortcut is that the "arrow form" can be used without the element list when only attributes apply. This is handy for tags without an end tag:

[Input new -> {type button 
               name "my button" 
               onclick "doSomething()"}]

Of course, it doesn't work when there are elements but no attributes. In that case using -Es is needed:

[Div new -Es [subst {element-list}]

Note that elements in element-list are TclOO objects, which is the reason the [subst {...}] command was necessary where the list contains [tag new ...] because tag new creates an object which is a command. Element lists can use [list ...] as well:

[Div new -As {class mydiv} -Es [list \
    [P new -x {My paragraph ...}] \
    [Div new -x {Another bunch of text...} -es [list \
        [P new -x {Inside the inner div}] \
        [Div new -> {class purplediv
                     id purpleID} [list \
            [Div new -Es [list \
                [Span new -x {This one IS a shape-shifter}] \
            ]] \
        ]] \
        [Input new -> {class greeninput
                       name greenish
                       id greenishID}] \
    ]] \
]]

[Div new -As {class mydiv} -Es [subst {
    [P new -x {My paragraph ...}] 
    [Div new -x {Another bunch of text...} -Es [subst {
        [P new -x {Inside the inner div}] 
        [Div new -> {class purplediv
                     id purpleID} [subst {
            [Div new -Es [subst {
                [Span new -x {This one IS a shape-shifter}]
            }]] 
        }]] 
        [Input new -> {class greeninput
                       name greenish
                       id greenishID}] 
    }]] 
}]]

Sometimes [list ...] works just as well, but problems can arise when elements have elements. With deeply nested or lengthy element lists, profusion of backslashes and close brackets can make it difficult to follow the logic and assure that tag invocations are properly terminated.

The [subst { }] convention is more distinctive re: braces marking "blocks" of element objects and easier for humans to follow. For these reasons it's the preferred syntax used throughout jWebTools itself.

Using the HTML class

The Html class implements the html element. Its constructor takes a few arguments: - "lang" which defaults to "en-us", - "title" referring to page title set in <head>, - "outfile" defauting to "default", and the major arguments, - "head" and "body" which are expected to be objects implementing these html sections.

A web page subclasses Html like this:

oo::class create MyWebPage {
    superclass Html

    constructor {} {
        next -head [my mkHead] -body [my mkBody] \
             -outfile mywebpage.html
    }

    method mkHead {} {...}
    method mkBody {} {...}
}

The contents of head and body are provided by mkHead and mkBody. This structure makes it easier to keep page creation organized and track changes to head or body.

Head class

Head is more complex than the classes described so far. The constructor has the familiar -elements/-Es argument but does not accept -attrs/-As since <head> elements don't have attributes.

Head also takes as an alternative to -elements, the arguments metas, links, scripts, styles, preloads which by default are empty lists. The idea is that lists of Meta, Link, etc., objects can be given as arguments, or a list of any or all these objects given as an -elements argument. (These classes are described below.)

The Head class also provides methods, addLinks, addPreloads, etc., which can be called by a Head object to extend the -elements list.

However the -elements argument is probably the most useful because it permits having elements appear in a known order which is useful for preloading and optimizing the page for performance. A typical Head invocation would then look similar to this example:

Head new -title "Open source software" -Es [subst {
    [Description new "Open source software developed at Thinairarts, including jWebTools project."]
    [Preload new -pos 0 -href /js/comment+menu.min.js -as script]
    [Preload new -pos 1 -href /css/software-all.min.css -as style]
    [Link new -href /css/software-all.min.css -rel stylesheet \
        -type text/css]
    [Link new -rel icon -href /favicon.ico -type image/icon]
}]

This becomes the head section of <html> element:

<head>
    <meta charset="utf-8">
    <title>Open source software</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="Open source software developed at Thinairarts, including jWebTools project.">
    <link rel="preload" href="/js/comment+menu.min.js" as="script">
    <link rel="preload" href="/css/software-all.min.css" as="style">
    <link rel="stylesheet" href="/css/software-all.min.css" type="text/css">
    <link rel="icon" href="/favicon.ico" type="image/icon">
</head>

Other classes in ooHead.tcl

Brief descriptions of Head classes useful in Head invocations:

Pagetitle takes only one argument: the page title as text.
Charset takes one argument: the charset, e.g., "en-us".
Meta has two options: -name, -content. Both default to "".
Description take one argument: the page description (text).
Link has options: -rel, -href, -as, -type, -srcset, -sizes, -other. All are "" by default. Other is used for arguments not predefined.
Preload Arguments include -pos (default:0), -href, -as, -type, -srcset, -sizes, -other. Pos determines order of preloads, as in 0, 1, etc. The other arguments are same as in Link.
Script Arguments are -src, -txt/-x, -As (attributes). Note that "-src" and "-txt" (script text/source) are mutually exclusive, only one of the two args is allowed.
Style Only one argument available: -txt (text of style content).

Body class

Like other "ordinary" tags, Body is a tag subclassed from Element and takes -attrs and -elements arguments exactly as described above.

The CmdArgs interface

It's probably obvious but most of the classes already discussed have a particular convention for entering arguments to constructors.

For example, tag classes accept arguments like -attrs (aka -As,-as) and -elements (aka -Es,-es). This is similar to most Tcl commands that accept named arguments prefixed with a dash.

jWebTools includes a very useful utility namespace implemented in CmdArgs.tcl. The facilities provided by CmdArgs are used in most jWebTools classes including the more complex compound (or composite) elements which have numerous options using named argument style.

Using CmdArgs

To access CmdArgs insert "package require jwebtools" at the top of a Tcl source file. For convenience, "namespace import CmdArgs::*" allows use of exported procs without qualifiers.

The main functionality of CmdArgs resides in procs chkArgs and chkArgsCi. The only difference is the latter enables case-insensitive named arguments to callers of the proc or method using chkArgsCi. For example, -myarg, -MYARG, -myArg, etc., would all work identically. The term chkArgs* refers to both variations.

chkArgs* takes one to five arguments. The first is required, usually the Tcl special argument "args" which is almost always used in the proc invoking chkArgs*.

The other arguments consist of one or more of the following: - -dbool {boolean_arg* 0/1}, one or more boolean named argument/value pairs. - -dnum {numeric_arg* <number>}, one or more numeric argument/value pairs. - -dstr {string_arg* <string>}, one or more string argument/value pairs. Note that <string> includes empty string, lists, etc. - -denum {enumerated_arg* <{enum list}>, one or more enum argument/value pairs. Note an enumerated value is a list of values defining exactly what values are accepted for the named argument. First value in the list is the default for the argument.

This example should help clarify how this works:

proc getInput {args} {
    CmdArgs::chkArgsCi $args -dbool {boolArg 0} \
        -dnum {numArg 123.4 numArgA 3} \
        -dstr {strArg "" strArgA "my string" strArgB {a little list}} \
        -denum {enumArg {1 2 3} enumArgA {cat dog bear}}

    puts $boolArg
    puts "$numArg, $numArgA"
    puts "$strArg, $strArgA, $strArgB"
    puts "$enumArg, $enumArgA"
}

getInput -boolArg -numArgA 249 -strARGB {Quite another LIST} -denumarga bear

Output:
    1
    123.4, 249
    , my string, Quite another LIST
    1, bear

The example shows that named arguments set up by chkArgs* become local variables in the proc using CmdArgs. The variable name isn't affected by how the named arg is given in the proc call. IOW getInput was called with the argument "-denumarga bear". But indeed enumArgA was set with value "bear" exactly as expected.

If the argument had been "-denumarga coyote" getInput would have aborted with an error since "coyote" was not a member of the enumeration. Similarly -dbool arguments require a Tcl boolean value, and -dnum arguments must be an acceptable number. Strings are generic data types in Tcl, so most any value will be accepted. Additional checks on values can be made on generated variables resulting from the chkArgs* call.

CmdArgs can be extremely useful in many Tcl programs, particularly when it's intended for a proc or method to have multiple optional arguments.

Compound elements in jWebTools

Several higher level compound elements are provided in jWebTools. These classes implement elements more complex than the basic tags but not as elaborate as true "widgets". These compound elements are intended to be time-saving features for typical website construction.

Menu consists of 4 classes: Menu, Submenu, MenuItem and MenuMask

The Menu class

Menu is a simple class taking arguments passed to its base class (which is Div). *Menu has fixed attributes, as -attrs {class menu-block}. IOW a Menu serves as a "backbone" for a collection of submenus and/or menuitems.

The Submenu class

Submenu is more elaborate than Menu. Named args include:

The MenuItem class

A MenuItem implements an item's action, consisting of an <a> element that redirects to a particular URL. Of course that can be on the same page, refer to another page in the same site or in fact any URL reachable on the internet. The practicality of "jumping" to a given URL is for a site to determine.

MenuItem arguments:

The MenuMask class

MenuMask argument:

Creating menus in practice

Constructing a menu usually takes a straightforward form:

set mymenu [Menu new -Es [subst {
    [Submenu new -level 0 -arrow left -title "Galleries" -menuitems [subst {
        [MenuItem new -label Monotypes   -href /gallery/monotypes.html]
        [MenuItem new -label Photography -href /gallery/photography.html]
        [MenuItem new -label Screenprints -href /gallery/screenprints.html]
    }]]
    [Submenu new -level 0 -arrow left -title Projects -menuitems [subst {
        [MenuItem new -label Artwork -href /artwork.html]
        [Submenu new -level 1 -arrow left -title Software -menuitems [subst {
            [MenuItem new -label jWebTools -href /software/jWebTools.html]
            [MenuItem new -label Stereograms -href /software/stereograms.html]
        }]]
    }]]
}]]

In mybody.tcl
[Body new -As {onbeforeunload on_leaving_page()
               onkeydown {filterkey(event,'toggle')}} -Es [subst {
    [MenuMask new -menu $mymenu]
    [Div new -> {class MainDiv} [subst {
        ....
    }]]
    [Script new -src /js/menu+xhr.min.js]
}]]

Making menus work

Menu objects require javascript participation in order to function. (Yes, there are pure CSS techniques for producing functional menus. However a certain level of CSS complexity is inevitable. Not that js is necessarily simpler, it does however offer flexibility and established integration with the classes/objects of jWebTools.)

(The js files are found in the js directory in the jWebTools extension. Specifically menu+xhr.min.js needs to be copied to the js directory under the $Config::PlatformRoot directory, which is of course fully configurable to the local installation.)

In the above example the js script was loaded into the Script element with the -bodyend argument. This loads the script at the end of the Body element. A couple of events are connected to <body> in Body's attribute list including mouse clicks that bring up or dismiss the menu.

Menu location, size, etc., are set in CSS via menu-vert.css. (This file needs to be copied to /css in your site's PlatformRoot location.)

WebImg class

WebImg is an <img> element enclosed in a <figure> element. WebImg has many options allowing for versatile use. Options include: - -src, Path to image file location - -srcset, -sizes, For image optimization - -caption, Text useed for image caption - -alt, Text to display when image not available - -imgClass, Class name of image

In addition, the css method of WebImg takes these options:
String options: - -width Width of WebImg (with unit, px, em, etc.) - -fontsize Size of font for caption - -height Height of element - -margin Specified as {top right bottom left} - -padding Specified as {top right bottom left} - -background Element background: color, etc.

The -media_* arguments allow specifying WebImg properties at various "media widths" (or screen widths). The syntax is {property media-width ...}, as in media_width == {1000px 650 800px 450}. (The media-width is always in px, so unit isn't written out.) - -media_margin - -media_width - -media_height - -media_float - -media_captionside - -media_fontsize

The -stub* arguments are used for article "stubs" or short excerpts for display and selection of full articles. Properties of WebImgs that appear in stubs are set by these args. - -stubwidth - -stubmargin - -stubmedia_margin - -stubmedia_width - -stubmedia_height - -stubmedia_float

Enumerated options: (first option is default) - -float {none left right} - -captionside {center left right} - -stubfloat {none left right}

It's pretty obvious most of the options are for CSS styling, reflecting the bulk of effort in preparing a website on the whole.

The usual routine use of WebImg involves opening a file to collect CSS configuration, typically pagename-img.css. The file is opened for writing and the file handle is used after the WebImg object is instantiated as shown in this example:

set camB [WebImg: -src /img/cameliaB-465x333.jpg \
    -srcset {/img/cameliaB-233x167.jpg 233w,
            /img/cameliaB-465x333.jpg 465w,
            /img/cameliaB-698x500.jpg 698w} \
    -sizes {(max-width: 625px) 50vw,(max-width: 1000px) 30vw, 30vw} \
    -hRes 698 -vRes 500  \
    -caption {"J Altfas" "Camelia B" screenprint 2020} \
    -alt "SIS, abstract faces"]
$camB css -margin 0 -height $ht
$camB writecss $fdcss 

"camB" is the WebImg object used to set the CSS parameters. The the object calls "writecss" to output CSS to the file. The "componentized" CSS coordinates with HTML output achieved with the "write" method of all Element derived elements. Thus any number of WebImg can appear on the same page without interference among them.

HTML output:

<figure s415205="" class="web-img-div">
    <div style="--aspect-ratio:1.396;">
        <div class="innerbox">
            <img src="/img/cameliaB-465x333.jpg" srcset="/img/  cameliaB-233x167.jpg 233w,
            /img/cameliaB-465x333.jpg 465w,
            /img/cameliaB-698x500.jpg 698w" sizes="(max-width: 625px) 50vw,(max-width: 1000px) 30vw, 30vw" alt="SIS, abstract faces">
        </div>
    </div>
    <figcaption s353826="" class="f-caption">
        "J Altfas" "Camelia B" screenprint 2020
    </figcaption>
    </figure>

CSS output:

.web-img-div[s415205] {
    height:18.15em;
    width:25.34em;
    margin:0;
    float:none
}
.f-caption[s353826] {
    text-align:center
}

Slider class

Slider is a more elaborate compound element, probably could be considered a "widget" but it doesn't really matter what it's called.

The mission of Slider is displaying a number of WebImgs one at a time with a nice transition between.

The Slider constructor has several argument options {default}:

So it's evident that most of the machinery is "under the hood" and not requiring user input. The most important options are width, height, and imgls.

Setting up a Slider is pretty straightforward. First create list of WebImg objects and associated CSS file. Generally the height of WebImgs is slightly smaller than Slider height, ~1.35em difference has worked well.

With the Slider object, call "sliderCss", writing the method output to the CSS file. Finally, the finished slider is generated with "mkSlider". Javascript is needed for auto-rotating slides. That's generated by "sliderJS". Slider is "componentized" in case a page has more than one slider.

Here's an example:

set fdcss [open css/main-img.css {WRONLY APPEND CREAT TRUNC}]
puts $fdcss "@import \"/css/main-all.css\";"
puts $fdcss "@import \"/css/web-image.css\";"
my mkSliderImgs 18.15em $fdcss
set sldr [Slider new sldr -imgls $SliderImgs -width 30 -height 19.5]
puts $fdcss [$sldr sliderCss]  
set myslider [$sldr mkSlider] ;# slider to display
set SldrJs [sldr sliderJs] ;# js to rotate slides
...
# place myslider in the page
Body new -As [list onload "${slshwID}(5)"] ... \
         -Es [subst {
    ...
    [div .... -Es $myslider ]
    ...
}]]

Using the jWebTools backend web server

jWebTools provides a web server intended primarily as a backend or reverse proxy server running with the nginx web server. Understandably users might want or need to use a main server other than nginx. This is certainly possible, although configuration for such products is going to be different than described here for nginx.

jWebTools's server is contained in the class JWTserv which has a few of options:

In practice these arguments are seldom used. Rather configuration options are set using Config namespace variables. More on Config below.

However, once Config overrides are provided, running the server in standalone mode is very simple:

package require jwebtools
source /path/to/localconfig.tcl #; Config overrides
server

That's all that's needed for a standalone server during website development. Assuming the listenPort is 8181, and PlatformRoot is the directory where software.html is located, pointing a browser to localhost:8181/software.html should display the software.html example page.

Builtin dynamic objects include CommentRecv and ContactRecv. These correspond to urls: "/comment-recv.html" and "/contact-recv.html". "Dynamic classes" subclass Dynpage by defining a method: "createResponse {hdrs} {...}". The argument "hdrs" is a dictionary with the usual array of http headers that the response may need to consult. In any case, most any dynamic content can be created and served including completely synthesized pages with or without database support.

For nginx reverse proxy, the following is a basic nginx configuration example:

server {
    listen  443 ssl http2;
    listen [::]:443 ssl http2;

    ssl_certificate /etc/letsencrypt/live/YOUR-SITE.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/YOUR-SITE.com/privkey.pem;

    server_name YOUR-SITE.com;
    root /usr/local/prog/www;
    index main.html index.html;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'self'" always;
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";

    location ~* .(jpg|jpeg|png|gif|ico|css|js)$ {
            expires 365d;
            #aio on;
        }

    location / {
        try_files $uri $uri/ @proxy;
        add_header Cache-Control public;
        add_header Pragma public;
        add_header Vary Accept-Encoding;
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
        add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'self'" always;
        add_header X-Frame-Options "SAMEORIGIN";
        add_header X-Content-Type-Options "nosniff";
        expires 365d;
    }

    location @proxy {
        proxy_pass http://127.0.0.1:8181;
        proxy_set_header Host $host;
        proxy_set_header X-Remote-IP $remote_addr;
        proxy_set_header X-Remote-Port $remote_port;
        proxy_set_header X-Remote-User $remote_user;
        proxy_set_header X-Path $uri;
        proxy_set_header X-Qstr $args;
        proxy_set_header Connection $http_connection;
    }
}

Configuration using the Config namespace

jWebTools uses the variables in the Config namespace to configure jWebTools. By default many of the options are placeholders which means values must be overridden before jWebTools will perform tasks as expected.

Local configuration can most easily be accomplished by creating a file such as "localconfig.tcl" which can be sourced where needed. Most often that includes running the jWebTools server (in standalone or reverse proxy mode), and prior to rendering *.html files from page classes (or namespace equivalents—see below). A typical localconfig.tcl:

# localconfig.tcl

set Config::contactRcvTo   jsmith@myisp.net 
set Config::contactRcvFrom jjones@anotherisp.com
set Config::PlatformRoot   C:/Users/USER/usr/local/prog/www
set Config::MsgFilePath    C:/Users/USER/usr/local/prog/messages/messages.txt
set Config::ServerLogPath  C:/Users/USER/usr/local/prog/logs/jwebtools.log
set DBPATH C:/Users/USER/usr/local/prog/db
set Config::Blist_db       $DBPATH/blist.db3
set Config::Comment_db     $DBPATH/comment.db3
set Config::SmtpConfigPath \
    C:/Users/USER/usr/local/prog/my-website-source/.tclnot.conf
set LOCBIN C:/Users/USER/usr/local/bin
set Config::ExpandCss      "pwsh $LOCBIN/expandCSS.ps1"
set Config::ExpandMinCss   "pwsh $LOCBIN/expand+minifyCSS.ps1"

Note that the example reflects use on a Windows device. If site development requires using both Windows/Unix platforms, it's recommended to set up a file structure parallel to unix-like systems in the user's home directory. Doing so makes tracking files on local/remote machines much easier.

Using the pmake system

jWebTools provides the basic infrastructure for setting up automatic rendering of page (*.html) files. This is somewhat like the "make" utility in Unix and other OS. In jWebTools, the class Pmakebase can be subclassed to create a site-specific Pmakefile implementing logic for rendering the sites pages.

Pmakebase defines building blocks useful in a Pmakefile. Basically Build and BuildX render one and multiple pages respectively. Dependency checking is provided by chkDeps which signals whether a file needs to be (re-)rendered or not.

In addition, CSS file combining and minifying are accomplished via methods called from method "cssall". For this to work CSS dependencies must be set up in a method "cssTargDeps" in Pmakefile.

Of course it's perfectly possible to use any of a great number of various make-like tools to accomplish site rendering. And even with Pmakefile different approaches are possible to creating a rendering pipeline. The algorithms chosen will reflect how pages are written.

This example shows some possible Pmakefile methods:

# ooPmakefile.tcl
package require jwebtools
source localconfig.tcl

oo::class create Pmakefile {
    superclass Pmakebase

    method cssTargsDeps {} {
        return {
            std-all.css { std.css comment.css style.css 
                    menu-vert.css}
            gallery-all.min.css { gallery.css std-all.css}
            contact-all.min.css {contact.css std-all.css}
            software-all.min.css {software.css std-all.css} 
            main-all.css {main.css style.css menu-vert.css
                    slider.css}
            main-img-all.min.css {main-img.css main-all.css
                    web-image.css}
            recipe-all.min.css {recipe.css std-all.css}
            rcp*-img-all.min.css {rcp*-img.css recipe-all.css
                    web-image.css}
        }
    }

    method main {{uncond 0}} {
        my Build -filenm ooMain.tcl -cls Main -fmt "" \
            -obargs write -outfile main.html -imgsuff \
            -deps {Menu Html Head Body WebImg} \
                -verbose 1 -uncond $uncond

    # other build methods...

    method all {args} 
        CmdArgs::chkArgsCi $args -dbool {uncond 0} \
            -denum {verbose {1 0 2}}

        my main $uncond
        # call other build methods
        # note that Build only runs if chkDeps -> >= 1

        my cssall -uncond $uncond -verbose $verbose 
    }
}

When run, pmake.tcl instantiates Pmakefile and calls the method entered on the command line (for instance, "pmake all").

On Unix platforms use "chmod a+x pmake.tcl" and symlink to /usr/local/bin/pmake. On Windows pmake.ps1 calls pmake.tcl. It's necessary to put pmake.ps1 in the executable path and that its call to "tclsh ..." is using the right path to tclsh and pmake.tcl. Example pmake.tcl and pmake.ps1 are included in the shell subdirectory of jWebTools. Once paths are properly set, then it's only necessary to invoke "pmake all" (or similar) at a shell prompt in the project directory to render all out-of-date html and css files.

Using the Tag:: namespace

To make using the classes in jWebTools a bit more convenient, jWebTools provides the Tag:: namespace.

Most classes are represented in Tag as procs that create and return instantiated objects. These procs accept the arguments used by the class constructor. Tag procs are named for the class with a colon (:) appended.

So for example, <div> tags are created using "Div:" equivalent to "Div new". Of course it saves a few keystrokes and also makes code look a bit cleaner. It also makes it more amenable to incorporating into an "ordinary" namespace, mitigating the need for using TclOO syntax.

For instance, a typical web page could be written like this:

package require jwebtools

oo::class create MyPage {
    superclass Html

    constructor {} {
        namespace import ::Tag::*
        next -head [my mkHead] -body [my mkBody] \
             -outfile mypage.html
    }

    method mkHead {} {
        Head: -Es [subst {
            [Title: "My Page"]
            [Preload: ...]
            ...
        }]
    }

    method mkBody {} {
        Body: -> {onclick "doSomething()"} [subst {
            [Div: -> {class bigdiv} [subst {
                [P: -x {Some text talking about something}]
                ...
            }]]
            ...
        }]
    }
}
# make mypage.html
[MyPage new] write

Alternatively, the page might be written in this way:

package require jwebtools

namespace eval MyPage {
    namespace import ::Tag::*

    proc construct {} {
        variable html
        set html [Html: -head [mkHead] -body [mkBody] \
            -outfile mypage.html
    }

    proc render {} {
        variable html
        construct
        $html write
    }

    proc mkHead {} {
        Head: -Es [subst {
            [Title: "My Page"]
            [Preload: ...]
            ...
        }]
    }

    proc mkBody {} {
        Body: -> {onclick "doSomething()"} [subst {
            [Div: -> {class bigdiv} [subst {
                [P: -x {Some text talking about something}]
                ...
            }]]
            ...
        }]
    }
}
# make mypage.html
MyPage::render

One small issue is where to put "namespace import ::Tag::*". Using class definitions, it doesn't work to put the import statement outside of methods or the constructor. Put inside the constructor or methods does seem to make Tag::* available. It's also works when placed at the global level. Using the namespace style placing the import at the namespace top level works without difficulty.

No doubt jWebTools users will find other, and even more creative ways to use these capabilities for producing web pages.

Messages and Comments

jWebTools includes facilities for website visitors to leave messages and comments (on comment-enabled pages). These services provide dynamic content. These implement basic functionality, but illustrate how dynamic content works in jWebTools and obviously much more interesting pages can be created.

Messages are mediated by ooContact.tcl and respective contact.html. Simple from visitor's perspective, simply fill out the form and press "send". The server receives the form data with URL of "contact-recv.html". The dynamic (virtual) page is processed by the ContactRecv object that does several things.

ContactRecv organizes the data (extracted from headers), and saves in a designated messages.txt file. Also, if configured, an email notification is sent to an administrator with message content. Finally, a response is returned to the client browser with text stating the message was received. (The returned text is configurable.)

The comment system uses the same basic mechanisms as used for messages. Some additional steps are necessary for a page to have commenting.

Comments use the Contact page for inputting comments. However when the comment is submitted, CommentDb is first used to extract and save the comment to the sqlite3 database by default kept in a file named comments.db3 in the db directory (per Config). (An empty comments.db3 database file is provided in the /path/to/jwebtools/db directory. Copy this file to the path specified in your localconfig.tcl Config::Comment_db variable.)

Feedback is provided to visitors through the comment section. By default the initial server response is text stating the site moderation policy.

As with messages, an administrator or moderator is notified by email when a comment is submitted by a visitor. The email contains the comment's text. The comment can then be approved by a moderator using the "approve" command (via symlink of approve.tcl to /usr/local/bin/approve).

Configuring a page for commenting requires use of javascript functions contained in CommentJsFunc. Not complicated to do:

# in a page class ...

method mkBody {} {
    Body: -Es [subst {
        [Div: -> {class main} {
            ...
            [CommDiv: "mypage"]
        ]
        [CommScript: "mypage"]
    }
}

Note that CommDiv is at the end of the page's main <div>, and the enabling js script is placed at the very end of <body>. These scripts enable server response through javascript file, comment+menu.min.js, which needs to be copied to the js directory under $Config::PlatformRoot.

Blog/Article Pages

Articles/blogs can be conveniently created using the facilities provided by classes Bsrc, Blist and BsrcStub. Bsrc takes a .bsrc file and renders a page according to the content in *.bsrc as described below. Information about the article is kept in a sqlite3 database the location of which can be configured in Config (or localconfig.tcl).

The .bsrc file

Articles are written as .bsrc files. A .bsrc is a Tcl file that contains a variable, "bsrc", which is set to a nested list compatible with Tcl dict. The list structure is as follows:

set bsrc {
    hdr {
        title {
        -Title {Article Title}
        -BlogDate {Mon, 16 Oct 2017 13:32:14 }
        -Descr {About role of articles in creating websites}
        }
        imgs {
            A {
                fig {
                    -src /img/jra-400x305.webp 
                    -srcset {/img/jra-133x102.webp 133w,
                            /img/jra-267x203.webp 267w,
                            /img/jra-400x305.webp 400w} 
                    -sizes {(max-width: 608px) 60vw,(max-width: 1000px) 30vw}
                    -hRes 400 
                    -vRes 305
                    -Alt {Abstract monotype depicting fission reactor}
                    -Caption {JRA, <i>Flux #4</i>, monotype, 2015}
                }
                css {
                    -Width 20em 
                    -Media_Width {15em 450 17.5em 625} 
                    -float left 
                    -Margin {1.25em 1.5em 0.5em 0}
                    -StubWidth 20em
                    -StubMargin {1em auto 0}
                }
            }
            B {
            }
        }
        pre {
            -href /img/jra-400x305.webp
            -as image
            -type image/webp
            -srcset {"/img/jra-133x102.webp 133w,
                    /img/jra-267x203.webp 267w,
                    /img/jra-400x305.webp 400w"}
            -sizes {" (max-width: 608px) 55vw,(max-width: 1000px) 25vw"}
        }
    }
    text {
        FigA

        Art is about many things. ...
        ...
    }
}

Bsrc is set to a list with two sublists, "hdr" and "text". Hdr contains lists "title", "imgs" and "pre". Title contains a list of paired attributes for article title, date and description. Imgs can contain specs for multiple images. Each image has a label, "A" in the example, and with the label a lists of fig and css specs. (These match WebImg requirements.) The "pre" list sets Html Head preload options for the page. If the article doesn't contain any images, the imgs and pre lists can be omitted.

The text list contains the article's text content and references to images included in imgs list. The image labeled A is referenced as "FigA". Size, placement, etc., of images is set in the css list for the image.

In the process of rendering the article, the bsrc variable is used to instantiate a dictionary. The format of the .bsrc file was designed to support nested dictionaries. It's important that the .bsrc uses the proper structure.

The .stub files (generated when rendering articles) are useful for example in landing-page summary sections containing article teasers. The .stub is rendered html, ready to be inserted. The page incorporating the .stub should include the corresponding *.stub.css file if the .stub includes images.

Rendering the blog/article page from a .bsrc file occurs when the Bsrc class (or derived class) is instantiated. For example, in a tclsh session:

% package require jwebtools
% source localconfig.tcl
% set bsrc [Bsrc new -bsrcfile article20220801.bsrc ...]
% $bsrc write

The result should be article20220801.html written to the output directory. The source directory will have article20220801.stub when rendering the page is complete.

Bsrc will add the article to the blist.db3 database, so the db should be set up before rendering. Basically the provided blist.db3 should be copied to the location specified in localconfig.tcl.

Blist class has methods categoryList, categoryAdd/Update, categoryDelete which allow adding/updating, deleting, listing article categories and title prefixes. The category is the same as the part of article title before 1st digit. Using categories simplifies storing/displaying articles/blogs and information about them.

% package require jwebtools
% set blist [Blist new]
% $blist categoryAdd/Update cars "Auto info" 
# if category cars doesn't exist it's added, otherwise title prefix updated

% $blist categoryList 
# prints list of categories, prefixes

% $blist categoryDelete cars
# category cars is deleted

Article organization

By default, articles are organized into source directory and rendered (html) file directory.

New articles are automatically added to the article database.

Summary

This document is intended to cover the major facilities of jWebTools in sufficient detail to allow experienced users to develop websites that meet even demanding requirements.

Of course there are bound to be rough edges at the current stage of jWebTools's development, but likely enough they're of minor consequence. Having used jWebTools quite extensively in constructing the website at https://thinairarts.com the author firmly believes no showstopper issues are present in jWebTools.

If there are errors or oversights in jWebTools or this documentation, please leave a message at https://thinairarts.com/contact.html describing the issue. It's optimum to leave a contact email in case more info is needed to resolve the problem.

Comments and constructive feedback are always welcome and very much appreciated!

Thank you! J Altfas
Sat 20 Aug 2022 00:14:09 PDT

doc.md:v0.5.0