WebStencils main goal is to significantly extend the existing web technologies in RAD Studio (WebBroker, DataSnap, RAD Server) by providing server-side scripting and turning RAD Server from a Web Service engine into a Web Site and Web Service tool. This means the ability to create HTML pages with standard tools and adopt any CSS and JavaScript libraries, while retaining the ability to add data from the generated pages coming from the application, like the result of a database query or any other complex data processing. 

In other words the main goal is to help with navigational and interactive websites: A blog, an online catalog, an online ordering system, a reference like a dictionary, and a wiki. These are all examples of navigational websites. In addition, WebStencils can be a good foundation for HTMX as a solution for web development. The two technologies go nicely together. HTMX pages can benefit from server-side code generation and hook into REST servers regarding content updates. Delphi web technologies can offer page generation and REST APIs at a very high-quality level.

From PageProducer to WebStencilsProcessor

Historically, the WebBroker and DataSnap libraries used the PageProducer component and derived ones to customize the HTML returned by a web server application. The processing was very limited and there was no simple way to have a shared template for the website pages. Also, the tag-based notation (<#custom attrib="value">) wasn't very easy to use. WebStencils offers a different notation (the previous tag will become @custom.value) that's easier to write and maintain into a complex HTML page, adds conditional and looping statements, and introduces a template and include mechanisms.

At the same time, the new WebStencilsProcessor components is a drop-in replacement for a PageProducer, as it implements the same interface and can be used in a WebBroker or DataSnap WebAction. You can add this component to a WebModule (the design-time data modules used by RAD Studio web applications), hook it to an action, and you'll be good to get started with it. Before I get to a demo, though, let's go over the syntax and a few other key elements of WebStencils.

This is an example of the WebStencilsProcessor component connected to an action in WebBroker:

122webstencils 03

The WebStencils Syntax

The WebStencils scripting language in RAD Studio has a rather simple syntax based on two elements:

  • The @ symbol as a special marker you add to HTML. The @ symbol is followed by:
    • The name of an object or field.
    • A special processing keyword (like foreach, if, layoutpage, loginrequired, loop, renderbody, include, query).
    • Another @ to output an actual @
  • Curly braces { } for optional or repeated block, used by conditional statements and loops

Accessing an Object Value

The general value access is based on a dot notation as  @myobject.value . What does this stand for?

The name of the object (myobjectis a symbolic local name. It can be define in multiple ways:

  • It can be a script variable, a symbolic named map to an actual object in your server side application and assigned to the WebStencilsProcessor before the script is executed
  • It can be a WebModule variable marked with an attribute (only in Delphi)
  • It can be resolved in code while processing an OnGetValue event handler of the specific WebStencilsProcessor component. In this case, you can look up the name in any data structure of your server application, with complete flexibility.

The name of the specific element of the object (value) can be mapped to:

  • The name of a property for the object itself
  • The name of a field if the object is a DataSet
  • A generic string passed to the OnGetValue event handler associated with the processor.

Accessing Values with the Dot Notation

Suppose you have an HTML WebStencils snippet with this code:

<h2>GetValue</h2>
<p>Value obtained from data: @data.name</p>

You can provide the data using a component with this event handler (the OnValue event):

procedure TWebModule13.WebStencilsValueFromEventValue(
  Sender: TObject; const ObjectName, FieldName: string;
  var ReplaceText: string; var Handled: Boolean)
begin
  if SameText (ObjectName, 'data') then
  begin
    Handled := True;
    ReplaceText := 'You requested ' + FieldName;
  end;
end;

In this simple case, the code checks only the object name (the first part of the symbol before the dot), but in general, you'd also want to check the FieldName, the symbol after the dot).

The alternative approach is to provide the values using an object. In this case, we must configure and associate the object with the action's producer.
This is a complete example, assuming a TMyObject class with a Name string property:

procedure TWebModule1.WebModule1WebActionItem2Action(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  WebStencilsProcessor1.InputLines.Text := '''
    <h2>GetValue</h2>
    <p>Value obtained from data: @world.Name</p>
    ''';
  var MyObj := TMyObject.Create();
  MyObj.Name := 'Hello hello';
  WebStencilsProcessor1.AddVar('world', MyObj, True);
  Response.Content := WebStencilsProcessor1.Content;
  Handled := True;
end;

The code defined the HTML template, using a Delphi multiline string, creates an object registering it as a script variable (calling AddVar), registered with the name 'world', and executes the script by accessing to its Content property. The result is "Value obtained from data: Hello hello."

Using a DataSet

The alternative option is to use the current record of a dataset rather than an object. In this case (given a ClientDataSet), your initialization code might look like this:

webstencils sourcecode dataset

This code registers a ClientDataSet with the name dataset. The matching HTML can have references to the object and some of the fields of each of the records, by using a foreach loop and the @loop WebStencils keywords to refer to the current element of the loop (that is, the current record):

highresolution webstencils code

This results in an output like the following output:

122webstencils 01

Templates: Layout and Body

A must-have feature of a template engine is the ability to merge a shared HTML template with the actual content of a page. This is accomplished using:

  • @LayoutPage filename : This command is used in a specific HTML page (generally at the top) to indicate which template file to use as the structure for the specific page. Multiple pages can share the same template, but not all pages in a project (even using TWebStencilsEngine) or a virtual folder need to use the same template. Notice you should use this only once on a page. If you attempt to use more than one @LayoutPage, it is blocked by an exception.
  • @RenderBody : This command (with no parameters) is a placeholder in a template file that indicates where to place the actual content of a specific page.

Here is an example of the use of @LayoutPage and @RenderBody in a specific page and a sample template. As you can see the page refers to the specific template, while the template has a general placeholder indicating where to merge in the page content. Notice that this is a reduce version of the HTML code used to generate the page shown above.

Test.html

   <h2>Page Test3</h2>
   <p>Local data param one is @page.name (the page name)</p>

BaseTemplate.html

  <head>
    <link href="https://.../bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
    <nav class="navbar navbar-expand-md bg-dark mb-4">...
    <main class="container">
      <div class="bg-light p-5 rounded">
      @RenderBody
      </div>
    </main>
  </body>
</html>

The Two WebStencils Components

To end this quick introduction to WebStencils (full coverage will easily take a small book!) I want to mention the two different components provided by the new WebStencils package:

  • The WebStencilsProcessor Component. This core component can take an HTML file, embed the WebStencils notation, and convert it to plain HTML after processing the @ tags.
    The component can processes an HTML string in memory or a file (and its associated template, if any). The processor may be

     

    • Used standalone and assigned to the TWebActionItem.Producer property
    • It can be created and called automatically by TWebStencilsEngine component to execute the actual processing
  • The WebStencilsEngine Component: This is the "engine" component, which can be used in two different scenarios:
    • You can connect it to one or more WebStencilsProcessor components to provide shared settings and behavior rather than customizing each specific processor.
    • You can let it create WebStencilsProcessor components when needed and place only this component on your web modules.

Here is a WebModule with both components:

122webstencils 02

I know this isn't terribly obvious at first sight, but overall the WebStencilsProcessor is used to process a single URL (and you can have many in an app) while the WebStencilsEngine can define it's own mapping of URLs to actual files to process, offering also a mechanism to execute specific server side code for each -- but that would require another blog post.

Introducing WebStencilsEngine

Up to now in this blog post we have seen simple demos using the Processors component associated directly or indirectly to an action in WebBroker. This is nice, but won't scale to a Web server with dozens or hundreds of different URLs. That's why there is also an Engine component. But how does it work? How to you map URLs to files to process and how do you associate specific code to be executed on the server? The WebStencilsEngine component has a PathTemplates collection property you can use to associate a URL path to a specific file. This is an example in a RAD Server package:

122webstencils 04

The PathTemplates collection property serves also a different purpose, which explains why there are value listed with not mapping (like company): You can associate to each item in the collection an event handle with initialization code to be executed when the URL matches, similarly to the code in the OnAction event handlers earlier. For example, the following code for the company URL checks if there is an ID parameter passed in the URL and saved in the script variables and uses it to locate a specific record in the dataset to display on the web page:

122webstencils 05

There is Way More

The WebStencils engine is fairly complex. There are more WebStencils keywords and I've covered here and dozens of properties, methods, and events in the two components. Here is the main Embarcadero DocWiki link for WebStencils, but we are working on extensive documentation and we know that one independent experts is working on a book. I might blog more, as well.