Skip to content

Advanced Usage

A lot of tasks can be achieved just using the basic features detailed above. However there are more advanced features that can make life even easier

Blocks

Blocks are a powerful dynamic content generation tool. WebDyne can render arbitrary blocks of text or HTML within a page, which makes generation of dynamic content generally more readable than similar output generated within Perl code. An example:

<html>
<head>
<title>Blocks</title>
</head>
<body>
<p>

<form>
2 + 2 = <textfield name="sum">
<p><submit>
</form>

<p>
<perl method="check">


<!-- Each block below is only rendered if specifically requested by the Perl code -->

<block name="pass">
Yes, +{sum} is the correct answer ! Brilliant ..
</block>

<block name="fail">
I am sorry .. +{sum} is not correct .. Please try again !
</block>

<block name="silly">
Danger, does not compute ! .. "+{sum}" is not a number !
</block>

<p>
Thanks for playing !

</perl>

</body>
</html>

__PERL__

sub check {

    my $self=shift();

    if ((my $ans=$_{'sum'}) == 4) {
        $self->render_block('pass')
    }
    elsif ($ans=~/^[0-9.]+$/) {
        $self->render_block('fail')
    }
    elsif ($ans) {
        $self->render_block('silly')
    }

    #  Blocks aren't displayed until whole section rendered
    #
    return $self->render();

}

Run

There can be more than one block with the same name - any block with the target name will be rendered:

<html>
<head><title>Hello World</title></head>
<body>
<p>
<form>
Enter your name:
<p><textfield name="name">
<p><submit>
</form>

<perl method="hello">


<!-- The following block is only rendered if we get a name - see the perl 
    code -->

<block name="greeting">
Hello +{name}, pleased to meet you !
<p>
</block>


<!-- This text is always rendered - it is not part of a block -->

The time here is !{! localtime() !}


<!-- This block has the same name as the first one, so will be rendered
    whenever that one is -->

<block name="greeting">
<p>
It has been a pleasure to serve you, +{name} !
</block>


</perl>

</body>
</html>

__PERL__

sub hello { 

    my $self=shift();

    #  Only render greeting blocks if name given. Both blocks
    #  will be rendered, as the both have the name "greeting"
    #
    if ($_{'name'}) {
        $self->render_block('greeting');
    }

    $self->render();
}

Run

Like any other text or HTML between <perl> tags, blocks can take parameters to substitute into the text:

<html>
<head><title>Hello World</title></head>
<body>
<p>
<form>
Enter your name: <textfield name="name">
&nbsp;
<submit>
</form>

<perl method="hello">


<!-- This block will be rendered multiple times, the output changing depending
    on the variables values supplied as parameters -->

<block name="greeting">
${i} .. Hello +{name}, pleased to meet you !
<p>
</block>

The time here is <? localtime() ?>

</perl>

</body>
</html>

__PERL__

sub hello { 

    my $self=shift();

    #  Only render greeting blocks if name given. Both blocks
    #  will be rendered, as the both have the name "greeting"
    #
    if ($_{'name'}) {
        for(my $i=0; $i<3; $i++) {
            $self->render_block('greeting', i=>$i );
        }
    }

    $self->render();
}

Run

Blocks have a non-intuitive feature - they still display even if they are outside of the <perl> tags that made the call to render them. e.g. the following is OK:

<html>
<head><title>Hello World</title></head>

<body>

<!-- Perl block with no content -->
<perl method="hello">
</perl>

<p>

<!-- This block is not enclosed within the <perl> tags, but will still render -->
<block name="hello">
Hello World
</block>

<p>

<!-- So will this one -->
<block name="hello">
Again
</block>

</body>
</html>

__PERL__

sub hello {

    my $self=shift();
    $self->render_block('hello');

}

Run

You can mix the two styles:

<html>
<head><title>Hello World</title></head>

<body>
<perl method="hello">

<!-- This block is rendered -->
<block name="hello">
Hello World
</block>

</perl>

<p>
<!-- So is this one, even though it is outside the <perl>..</perl> block -->
<block name="hello">
Again
</block>

</body>
</html>

__PERL__

sub hello {

    my $self=shift();
    $self->render_block('hello');
    $self->render();

}

Run

You can use the <block> tag display attribute to hide or show a block, or use a CGI parameter to determine visibility (e.g for a status update or warning):

<start_html>

<!-- Form to get block toggle status. Update hidden param based on toggle button -->
<form>
<submit name="button" value="Toggle">
<hidden name="toggle" value="!{! $_{'toggle'} ? 0 : 1 !}">
</form>

<!-- This block will only be displayed if the toggle value is true -->
<block name="toggle1" display="!{! $_{'toggle'} !}">
Toggle On (+{toggle})
</block>
<p>

<!-- This block will always display -->
<block name="hello" display=1>
Hello World
</block>

<!-- This block will never display unless called from a perl handler -->
<block name="hello" display=0>
Goodbye world
</block>

Run

File inclusion

You can include other file fragments at compile time using the include tag:

<html>
<head><title>Hello World</title></head>
<body>
<p>
The protocols file on this machine:
<pre>
<include file="/etc/protocols">
</pre>
</body>
</html>

Run

If the file name is not an absolute path name is will be loaded relative to the directory of the parent file. For example if file "bar.psp" incorporates the tag <include file="foo.psp"> it will be expected that "foo.psp" is in the same directory as "bar.psp".

Important

The include tag pulls in the target file at compile time. Changes to the included file after the WebDyne page is run the first time (resulting in compilation) are not reflected in subsequent output unless the nocache attribute is set. Thus the include tag should not be seen as a shortcut to a pseudo Content Management System. For example <include file="latest_news.txt"> will probably not behave in the way you expect. The first time you run it the latest news is displayed. However updating the "latest_news.txt" file will not result in changes to the output (it will be stale).

If you do use the nocache attribute the included page will be loaded and parsed every time, significantly slowing down page display. There are betters ways to build a CMS with WebDyne - use the include tag sparingly !

You can include just the head or body section of a HTML or PSP file by using the head or body attributes. Here is the reference file (file to be included). It does not have to be a .psp file - a standard HTML file can be supplied :

<start_html title="Include Head Title">
Include Body

Run

And here is the generating file (the file that includes sections from the reference file).

<html>
<head>
<include head file="./include2.psp">
</head>
<body>
<include body file="./include2.psp">

Run

You can also include block sections from .psp files. If this is the reference file (the file to be included) containing two blocks. This is a renderable .psp file in it's own right. The blocks use the display attribute to demonstrate that they will produce output, but it's not required:

<start_html>
<p>
<block name="block1" display>
This is block 1
</block>

<p>
<block name="block2" display>
This is block 2
</block>

Run

And here is the file that brings in the blocks from the reference file and incorporates them into the output:

<start_html>
This is my master file
<p>
Here is some text pulled from the "include4.psp" file:
<p>
<include file="include4.psp" block="block1">
<p>
And another different block from the same file with caching disabled:
<p>
<include file="include4.psp" block="block2" nocache>

Run

Static Sections

Sometimes you want to generate dynamic output in a page once only (e.g. a last modified date, a sidebar menu etc.) Using WebDyne this can be done with Perl or CGI code flagged with the "static" attribute. Any dynamic tag so flagged will be rendered at compile time, and the resulting output will become part of the compiled page - it will not change on subsequent page views, or have to be re-run each time the page is loaded. An example:

<html>
<head><title>Hello World</title></head>
<body>
<p>
Hello World
<hr>


<!-- Note the static attribute -->

<perl method="mtime" static="1">
<em>Last Modified: </em>${mtime}
</perl>

</body>
</html>

__PERL__

sub mtime {

    my $self=shift();
    my $r=$self->request();

    my $srce_pn=$r->filename();
        my $srce_mtime=(stat($srce_pn))[9];
    my $srce_localmtime=localtime $srce_mtime;

        return $self->render( mtime=>$srce_localmtime )

}

Run

In fact the above page will render very quickly because it has no dynamic content at all once the <perl> content is flagged as static. The WebDyne engine will recognise this and store the page as a static HTML file in its cache. Whenever it is called WebDyne will use the Apache lookup_file() function to return the page as if it was just serving up static content.

You can check this by looking at the content of the WebDyne cache directory (usually /var/webdyne/cache). Any file with a ".html" extension represents the static version of a page.

Of course you can still mix static and dynamic Perl sections:

<html>
<head><title>Hello World</title></head>
<body>
<p>
Hello World
<p>

<!-- A normal dynamic section - code is run each time page is loaded -->

<perl method="localtime">
Current time: ${time} 
</perl>
<hr>

<!-- Note the static attribute - code is run only once at compile time -->

<perl method="mtime" static="1">
<em>Last Modified: </em>${mtime}
</perl>


</body>
</html>

__PERL__


sub localtime {

    shift()->render(time=>scalar localtime);

}


sub mtime {

    my $self=shift();
    my $r=$self->request();

    my $srce_pn=$r->filename();
        my $srce_mtime=(stat($srce_pn))[9];
    my $srce_localmtime=localtime $srce_mtime;

        return $self->render( mtime=>$srce_localmtime )

}

Run

If you want the whole pages to be static, then flagging everything with the "static" attribute can be cumbersome. There is a special meta tag which flags the entire page as static:

<html>
<head>

<!-- Special meta tag -->
<meta name="WebDyne" content="static=1">

<title>Hello World</title>
</head>
<body>
<p>
Hello World
<hr>


<!-- A normal dynamic section, but because of the meta tag it will be frozen 
    at compile time -->

<perl method="localtime">
Current time: ${time} 
</perl>

<!-- Note the static attribute. It is redundant now the whole page is flagged
    as static - it could be removed safely. -->

<p>
<perl method="mtime" static="1">
<em>Last Modified: </em>${mtime}
</perl>


</body>
</html>

__PERL__


sub localtime {

    shift()->render(time=>scalar localtime);

}


sub mtime {

    my $self=shift();
    my $r=$self->request();

    my $srce_pn=$r->filename();
        my $srce_mtime=(stat($srce_pn))[9];
    my $srce_localmtime=localtime $srce_mtime;

        return $self->render( mtime=>$srce_localmtime )

}

Run

If you don't like the idea of setting the static flag in meta data, then "using" the special package "WebDyne::Static" will have exactly the same effect:

<html>
<head>
<title>Hello World</title>
</head>
<body>
<p>
Hello World
<hr>

<perl method="localtime">
Current time: ${time} 
</perl>

<p>

<perl method="mtime">
<em>Last Modified: </em>${mtime}
</perl>

</body>
</html>

__PERL__


#  Makes the whole page static
#
use WebDyne::Static;


sub localtime {

    shift()->render(time=>scalar localtime);

}


sub mtime {

    my $self=shift();
    my $r=$self->request();

    my $srce_pn=$r->filename();
        my $srce_mtime=(stat($srce_pn))[9];
    my $srce_localmtime=localtime $srce_mtime;

        return $self->render( mtime=>$srce_localmtime )

}

Run

If the static tag seems trivial consider the example that displayed country codes:

<html>
<head><title>Hello World</title></head>
<body>
<p>

<!-- Generate all country names for picklist -->

<form>

Your Country ?
<perl method="countries">
<popup_menu values="${countries_ar}" default="Australia">
</perl>

</form>
</body>
</html>

__PERL__

use Locale::Country;

sub countries {

    my $self=shift();
    my @countries = sort { $a cmp $b } all_country_names();
    $self->render( countries_ar=>\@countries );

}

Run

Every time the above example is viewed the Country Name list is generated dynamically via the Locale::Country module. This is a waste of resources because the list changes very infrequently. We can keep the code neat but gain a lot of speed by adding the static tag attribute:

<html>
<head><title>Hello World</title></head>
<body>
<p>

<!-- Generate all country names for picklist -->

<form>

Your Country ?
<perl method="countries" static="1">

<!-- Note the addition of the static attribute -->

<popup_menu values="${countries_ar}">
</perl>

</form>
</body>
</html>

__PERL__

use Locale::Country;

sub countries {

    my $self=shift();
    my @countries = sort {$a cmp $b} all_country_names();
    $self->render( countries_ar=>\@countries );

}

Run

By simply adding the "static" attribute output on a sample machine resulted in a 4x speedup in page loads. Judicious use of the static tag in places with slow changing data can markedly increase efficiency of the WebDyne engine.

Caching

WebDyne has the ability to cache the compiled version of a dynamic page according to specs you set via the API. When coupled with pages/blocks that are flagged as static this presents some powerful possibilities.

Important

Caching will only work if $WEBDYNE_CACHE_DN is defined and set to a directory that the web server has write access to. If caching does not work check that $WEBDYNE_CACHE_DN is defined and permissions set correctly for your web server.

There are many potential examples, but consider this one: you have a page that generates output by making a complex query to a database, which takes a lot of CPU and disk IO resources to generate. You need to update the page reasonably frequently (e.g. a weather forecast, near real time sales stats), but can't afford to have the query run every time someone view the page.

WebDyne allows you to configure the page to cache the output for a period of time (say 5 minutes) before re-running the query. In this way users sees near real-time data without imposing a high load on the database/Web server.

WebDyne knows to enable the caching code by looking for a meta tag, or by loading the WebDyne::Cache module in a __PERL__ block.

The cache code can command WebDyne to recompile a page based on any arbitrary criteria it desires. As an example the following code will recompile the page every 10 seconds. If viewed in between refresh intervals WebDyne will serve up the cached HTML result using Apache r$->lookup_file() or the FCGI equivalent, which is very fast.

Try it by running the following example and clicking refresh a few times over a 20 second interval

<html>
<head>
<title>Caching</title>
<!-- Set static and cache meta parameters -->
<meta name="WebDyne" content="cache=&cache;static=1">
</head>

<body>
<p>

This page will update once every 10 seconds.

<p>

Hello World !{! localtime() !}

</body>
</html>

__PERL__


#  The following would work in place of the meta tags
#
#use WebDyne::Static;
#use WebDyne::Cache (\&cache);


sub cache {

    my $self=shift();

    #  Get cache file mtime (modified time)
        #
        my $mtime=${ $self->cache_mtime() };


        #  If older than 10 seconds force recompile
        #
        if ((time()-$mtime) > 10) { 
                $self->cache_compile(1) 
        };

    #  Done
    #
    return \undef;

}

Run

WebDyne uses the return value of the nominated cache routine to determine what UID (unique ID) to assign to the page. In the above example we returned \undef, which signifies that the UID will remain unchanged.

You can start to get more advanced in your handling of cached pages by returning a different UID based on some arbitrary criteria. To extend our example above: say we have a page that generated sales figures for a given month. The SQL code to do this takes a long time, and we do not want to hit the database every time someone loads up the page. However we cannot just cache the output, as it will vary depending on the month the user chooses. We can tell the cache code to generate a different UID based on the month selected, then cache the resulting output.

The following example simulates such a scenario:

<!-- Start to cheat by using start/end_html tags to save space -->

<start_html>
<form method="GET">
Get sales results for:&nbsp;<popup_menu name="month" values="@{qw(January February March)}">
<submit>
</form>

<perl method="results">
Sales results for +{month}: $${results}
</perl>

<hr>
This page generated: !{! localtime() !}
<end_html>

__PERL__

use WebDyne::Static;
use WebDyne::Cache (\&cache);

my %results=(

    January     => 20,
    February    => 30,
    March       => 40
);

sub cache {

    #  Return UID based on month
    #
    my $uid=undef;
    if (my $month=$_{'month'}) {

        #  Make sure month is valid
        #
        $uid=$month if defined $results{$month}

    }
    return \$uid;

}


sub results {

    my $self=shift();
    if (my $month=$_{'month'}) {

        #  Could be a really long complex SQL query ...
        #
        my $results=$results{$month};


        #  And display
        #
        return $self->render(results => $results);
    }
    else {
        return \undef;
    }

}

Run

Important

Take care when using user-supplied input to generate the page UID. There is no inbuilt code in WebDyne to limit the number of UID's associated with a page. Unless we check it, a malicious user could potentially DOS the server by supplying endless random "months" to the above page with a script, causing WebDyne to create a new file for each UID - perhaps eventually filling the disk partition that holds the cache directory. That is why we check the month is valid in the code above.

JSON

WebDyne has a <json> tag that can be used to present JSON data objects to Javascript libraries in an output page. Here is a very simple example:

<start_html title="Sample JSON Chart" script="https://cdn.jsdelivr.net/npm/chart.js">

<h2>Monthly Sales Chart</h2>

<canvas id="myChart"></canvas>

<json handler="chart_data" id="chartData">

<script>
  // Parse JSON from the script tag
  const data = JSON.parse(document.getElementById("chartData").textContent);

  const ctx = document.getElementById('myChart').getContext('2d');
  new Chart(ctx, {
    type: 'bar', // You can also use 'line', 'pie', etc.
    data: {
      labels: data.labels,
      datasets: [{
        label: 'Sales',
        data: data.values,
      }]
    }
  });
</script>

__PERL__

sub chart_data {

    my %data=(
        labels  => [qw(Jan Feb Mar Apr)],
        values  => [(120, 150, 180, 100)]
    );
    return \%data

}

Run

If you run it and review the source HTML you will see the JSON data rendered into the page as <script></script> block of type application/json with an id of "chartData". Any data returned by the perl routine nominated by the json tag is presented as JSON within that tag block, and available to Javascript libraries within the page. JSON data is kept in canononical order by default, which can be adjusted with the WEBDYNE_JSON_CANONICAL variable if not desired/needed for a very small speed-up.

API Tags

WebDyne has the ability to make available a basic REST API facility using the <api> tag in conjunction with the Router::Simple CPAN module. Documents that utilise the <api> tag are somewhat unique in that:

  • There is no need for any other tags in the document besides the <api> tag. All other tags are ignored - in fact they are discarded.

  • Any .psp file file an <api> tag will only emit JSON data with a content type of "application/json"

  • The REST api path must correspond .psp file at some path level, e.g. if your path is /api/user/42 you must have a file called either "api.psp" or "api/user.psp" in your path.

  • A .psp file can contain multiple <api> tags corresponding to different Router::Simple routes

Here is a very simple example. Note the format of the URL in the Run hyperlink:

<api handler=uppercase pattern="/api/uppercase/{user}/:id">
<api handler=doublecase pattern="/api/doublecase/{user}/:id">
__PERL__
sub uppercase {

    my ($self, $match_hr)=@_;
    my ($user, $id)=@{$match_hr}{qw(user id)};
    my %data=(
        user => uc($user),
        id   => $id
    );
    return \%data

}

sub doublecase {

    my ($self, $match_hr)=@_;
    my ($user, $id)=@{$match_hr}{qw(user id)};
    my %data=(
        user => join('_', uc($user), lc($user)),
        id   => $id
    );
    return \%data

}

Run uppercase API example

Run doublecase API example

Caution

The <api> tag is still somewhat experimental. Use with caution

HTMX

WebDyne has support for <htmx> tags to supply fragmented HTML to pages using the HTMX Javascript Library. WebDyne can support just supplying HTML snippet to pages in response to htmx calls. HTMX and WebDyne are complementary libraries which can be combined together to support dynamic pages with in-place updates from WebDybe Perl backends. Here is a simple HTML file (htmx_demo1.psp) incorporating HTMX calls to a backend file called htmx_time1.psp. Here is the display file, htmx_demo1.psp

<start_html script="https://unpkg.com/htmx.org@1.9.10">
<h2>Current time</h2>
<p>Click the button below to load time data from the server</p>

<!-- HTMX Trigger Button -->
<button 
  hx-get="htmx_time1.psp"
  hx-target="#time-container"
  hx-swap="innerHTML"
>
Get Time
</button>

<!-- Where the fetched HTML fragment will go -->
<p>
<div id="time-container">
  <em>Time data not loaded yet.</em>
</div>

Run

And the backend file which generates the HTMX data for the above page (htmx_time1.psp):

<start_html>
<htmx>Server local time: <? localtime() ?> </htmx>

Run

Note the <htmx> tags. You can run the above htmx resource file and it will render correctly as a full HTML page - however if WebDyne detects a 'hx-request' HTTP header it will only send the fragment back.

Important

Only one <htmx> section from a file will ever be rendered. You can have multiple <htmx> sections in a .psp file however only one can be rendered at any time. You can use the display attribute with dynamic matching (see later) do render different <htmx> sections in a .psp file, or you can keep them all in different files (e.g. one <htmx> section per .psp file

Using Perl within <htmx> tags

<htmx> tags can be called with the same attributes as <perl> tags, including nominating a handler to generate data. See the following example:

<start_html script="https://unpkg.com/htmx.org@1.9.10">
<h2>Current time</h2>
<p>Click the button below to load time data from the server</p>

<!-- HTMX Trigger Button -->
<button 
  hx-get="htmx_time2.psp"
  hx-target="#time-container"
  hx-swap="innerHTML"
>
Get Time
</button>

<!-- Where the fetched HTML fragment will go -->
<p>
<div id="time-container">
  <em>Time data not loaded yet.</em>
</div>

Run

And the backend file which generates the HTMX data for the above page - now with the HTML fragment generated by Perl:

<start_html>
<htmx handler="server_time">
<p>
Server local time: ${server_time}
</htmx>

__PERL__

sub server_time {
    my $self=shift();
    my $time=scalar localtime;
    for (1..3) {
        $self->print( $self->render( server_time=> $time ));
    }
    return \undef;
}

Run

Using multiple <htmx> tags in one .psp file

As is mentioned above only one <htmx> fragment can be returned by a .psp page at a time - but you can use techniques to select which tag should be rendered. The <htmx> tag supports the display attribute. If this attribute exists and is a "true" value then the <htmx> fragment will be returned. At first this doesn't seem very useful - but when combined with dynamic evaluation via either page query parameters or !{! .. !} evaluation it becomes more compelling. Take the following two button example:

<start_html script="https://unpkg.com/htmx.org@1.9.10">
<h2>HTMX Demo</h2>
<p>
Click the button below to load time data from the server.</p>

<!-- HTMX Trigger Button for Local Time -->
<button 
  style="width:180px"
  hx-get="/htmx_time3.psp"
  hx-target="#time-container"
  hx-swap="innerHTML"
  hx-vals="js:{ time_local: 1 }"
>
Get Local Time
</button>

<p>

<!-- HTMX Trigger Button for UTC Time -->
<button 
  style="width:180px"
  hx-get="/htmx_time3.psp"
  hx-target="#time-container"
  hx-swap="innerHTML"
  hx-vals="js:{ time_utc: 1 }"
>
Get UTC Time
</button>

<!-- Where the fetched HTML fragment will go -->
<p>
<div id="time-container">
  <em>Time data not loaded yet.</em>
</div>

Run

<htmx display="+{time_local}"> Time Local: <? localtime() ?></htmx>
<htmx display="+{time_utc}"> Time UTC: <? gmtime() ?></htmx>

Run

Normally you would expect to have the hx-get attribute for each button go to a different .psp page. But in this instance they refer to the same page. So how do we discriminate ? The key is in the supply of the hx-vals attribute, which allows us to send query strings to the htmx resource page. We can then use them to select which <htmx> block is returned.

Note

Note the use of js:{ <json> } notation in the <htmx> hx-vals attribute. It allows for easier supply of JSON data without needed to manipulate/escape double-quotes in raw JSON data. You'll also note there is no <start_html> tag. It's not necessary for <htmx> pages.

Dump

The <dump> tag is a informational element which can be included in a page for diagnostic or debugging purposes. It will show various variable and state values for the page. By default if a <dump> flag is embedded in a page diagnostic information is not shown unless the force attribute is specified or the $WEBDYNE_DUMP_FLAG is set, the latter allowing the <dump> tag to be embedded into all pages on a site but not activated unless debugging enabled.

Various diagnostic elements can be displayed - see the <dump> tag section for information on what they are. In this example all components are enabled and display is forced:

<start_html>
<p>
<form>
Your Name: <textfield name="name">
<p>
<submit>
</form>
<dump all force>

Run

Dump display/hide can be controlled by form parameters or run URI query strings. In the example below ticking the checkbox or simply appending "?dump_enable=1" to the URL will display the dump information:

<start_html>
<p>
<form>
Your Name: <textfield name="name">
<p>
Show Dump: <checkbox name="dump_enable">
<p>
<submit>
</form>
<dump all force="!{! $_{'dump_enable'} !}">

Run