NaviServer Programmable Web Server

[ Main Table Of Contents | Table Of Contents | Keyword Index ]

ns_adp_register(n) 5.1.0 naviserver "NaviServer Built-in Commands"

Name

ns_adp_register - Creating Custom ADP Tags

Table Of Contents

Synopsis

Description

These commands enable the definition of custom ADP tags (application-specific, XML-like tags) which are expanded whenever ADP content (.adp files or ADP strings processed via ns_adp_parse) is processed. Registering application-specific ADP tags is a means to include script-driven, application-specific content in a web page, providing an alternative to embedding scripts in ADP pages via the <% script %> syntax as described in the ADP Overview man page.

Registered ADP tags use ordinary markup syntax and are recognized by the ADP parser as tags, not as ADP script blocks. The first character of a registered ADP tag name must be an ASCII letter or digit. The remaining characters may be ASCII letters, digits, colons, underscores, or hyphens. This accepts conventional HTML tag names, NaviServer's historical support for digit-start tag names, custom-element-style names such as my-widget, and existing OpenACS tag names such as listfilters-form. A tag name must not start with a hyphen. Examples of valid tag names are foo, adp, adp_value, h1, 2col, and listfilters-form. Tag names starting with characters such as $, %, @, or - are invalid, since such names cannot be recognized as registered tags by the ADP parser.

Registered tags can either be tags with content - consisting of an opening and a closing tag - or tags defining empty elements, where no closing tag is defined. When both the tag and the endtag are specified in the registering command, the tags must have content; when the endtag is omitted in the registering command, empty elements are defined.

When an endtag is specified, it must consist of a slash followed by the same tag name as the opening tag. For example, a start tag mytag must use the corresponding end tag /mytag.

Tag Sets

By default, registered ADP tags are stored in the server-wide default ADP tag table. This is the traditional NaviServer behavior.

The optional -tagset argument registers the tag in a named ADP tag set instead. Named tag sets are created and inspected with ns_adp_tagset. They can be selected later by commands such as ns_adp_parse and ns_adp_include.

The reserved tag-set name default refers to the built-in server-wide default tag table. Therefore,

 ns_adp_registerscript -tagset default mytag /mytag ::mytag_handler

is equivalent to:

 ns_adp_registerscript mytag /mytag ::mytag_handler

Named tag sets are useful when different ADP parsing modes must coexist in the same server, for example during a gradual migration from one registered tag vocabulary to another.

COMMANDS

ns_adp_registeradp ?-tagset value? tag ?endtag? adpstring

Registers an ADP snippet adpstring to be included when the specified tag is encountered while parsing the ADP content. The tag argument specifies the tag that will trigger the inclusion of the parsed ADP fragment adpstring.

If the optional endtag argument is specified, the content between the opening and closing tags is replaced by the adpstring, and the enclosed content is discarded. This behavior differs from the other tag registering commands, where the enclosed content can be processed or passed to the handler.

Note: Attributes specified in the tag are not accessible when using ns_adp_registeradp. If you need to access attributes or the enclosed content, consider using ns_adp_registerproc or ns_adp_registerscript.

 # Register an ADP snippet to display the current date as tag "printdate"
 ns_adp_registeradp printdate {
      The current date is: <% ns_adp_puts [ns_httptime [ns_time]] %>
 }

Usage example in an ADP page:

 ... header ...
 <p>This is my page.</p>
  <printdate>
 ... footer ...
ns_adp_registerproc ?-tagset value? tag ?endtag? proc

Registers a Tcl procedure to be called when the given tag is encountered in ADP content.

This command uses a simple positional calling convention. Only the attribute values from the start tag are passed to proc; attribute names are discarded. The values are passed in the order in which the attributes occur in the tag.

If endtag is specified, the procedure receives one additional final argument containing the literal content between the opening and closing tags. The enclosed content is not parsed before it is passed to the procedure.

The return value of proc replaces the tag, and also replaces the enclosed content when an end tag is registered.

Use ns_adp_registerproc when the tag handler only needs positional arguments, or when the attribute names are not relevant. Use ns_adp_registerscript when the handler needs structured access to attribute names and values via an ns_set.

 # Define the tag handler
 proc geturltag {args} {
    # Extract the URL from the last argument
    set url [lindex $args end]
    # Extract any options (attributes)
    set options [lrange $args 0 end-1]
    # Perform an HTTP GET request
    set response [ns_http run {*}$options $url]
    # Return the response body
    dict get $response body
 }
 
 # Register the tag handler with an opening and closing tag
 ns_adp_registerproc geturl /geturl geturltag

Usage example in an ADP page:

 ... header ...
 <p>This is my page.
 
 <geturl -timeout 3s >https://example.com/hello.html</geturl>
 
 <p>Next paragraph.</p>
 ... footer ...

Notes:

  • Attribute names are not passed to the procedure.

  • Attribute values are passed positionally.

  • The optional body is passed as the final argument when an end tag is registered.

  • This command is suitable for lightweight tags with a positional calling convention.

 proc ::reporttags {args} {
   return $args
 }
 ns_adp_registerproc reporttags  ::reporttags
 
 ns_adp_parse -string {<reporttags CamelCase x=X A=a>}
 # Returns: CamelCase X a
ns_adp_registerscript ?-tagset value? tag ?endtag? proc

Registers a Tcl procedure to be called when the given tag is encountered in ADP content.

This command uses a structured calling convention. Tag attributes are parsed into an ns_set containing name/value pairs, so the handler can inspect attributes by name. This is usually the preferred form for tags whose behavior depends on named options, optional attributes, or flag-like attributes.

If the tag is registered with an endtag, proc is called with two arguments:

  1. the literal content between the opening and closing tags

  2. the ns_set containing the tag attributes

When the tag is registered without an end tag, proc is called with one argument: the ns_set containing the attributes.

Attributes without an explicit value are stored in the ns_set with their name as the value.

The return value of proc replaces the tag, and also replaces the enclosed content when an end tag is registered.

Use ns_adp_registerscript when the handler needs attribute names, case-insensitive lookups, optional flags, or structured attribute handling. Use ns_adp_registerproc for simpler positional handlers.

 # Define the tag handler
 proc onday {string attributes} {
   # Get the 'day' attribute
   set day [ns_set get -nocase $attributes day]
   
   # Check if the current date matches the specified day
   if {[ns_fmttime [ns_time] "%m/%d"] eq $day} {
     return $string
   }
 }
 
 # Register the tag handler with an opening and closing tag
 ns_adp_registerscript onday /onday onday

Usage example in an ADP page:

 ... header ...
 <p>This is my page.</p>
 
 <onday day="12/25"><p>Merry Christmas and a Happy New Year</p></onday>
 
 <p>Next paragraph.</p>
 ... footer ...

Notes:

  • Attribute names and values are available via the ns_set passed to the procedure.

  • Attribute values can be accessed by name, for example with ns_set get or ns_set iget.

  • The optional body is passed separately from the attribute set when an end tag is registered.

  • This command is suitable for XML-like or HTML-like tags with named attributes.

Registerproc vs. Registerscript

The commands ns_adp_registerproc and ns_adp_registerscript both register Tcl procedures as ADP tag handlers, but they use different calling conventions.

ns_adp_registerproc

Passes only attribute values as positional arguments. Attribute names are discarded. When an end tag is registered, the tag body is passed as the final argument.

ns_adp_registerscript

Passes attributes as an ns_set, preserving attribute names and values. When an end tag is registered, the tag body is passed as the first argument and the attribute set as the second argument.

 proc positional_tag {args} {
     return "values: $args"
 }
 ns_adp_registerproc positional /positional positional_tag
 
 ns_adp_parse -string {<positional a=1 b=2>body</positional>}
 # The handler receives: 1 2 body
 
 proc structured_tag {body attributes} {
     set a [ns_set get $attributes a]
     set b [ns_set get $attributes b]
     return "a=$a b=$b body=$body"
 }
 ns_adp_registerscript structured /structured structured_tag
 
 ns_adp_parse -string {<structured a=1 b=2>body</structured>}
 # The handler receives the body and an ns_set containing a=1 and b=2.

EXAMPLES

The following example creates a named tag set, registers a tag in that tag set, and parses ADP using the named tag set.

 ns_adp_tagset create -fallback default demo-tags
 
 proc ::demo::hello_tag {body attributes} {
     return "Hello from demo-tags"
 }
 
 ns_adp_registerscript -tagset demo-tags hello /hello ::demo::hello_tag
 
 ns_adp_parse -tagset demo-tags -string {
     Before <hello>ignored body</hello> after
 }

Since demo-tags was created with -fallback default, tags not found in demo-tags are looked up in the default tag table.

The following is a simple way of handling conditional content in ADPs:

 # Store content in a global variable
 proc remember {input tagset} {
   set tagname [ns_set get -nocase $tagset name]
   if {$tagname eq ""} {
     set ::_adp_memory($tagname) $input
     return ""
   } else {
     return $input
   }
 }
 
 # Retrieve and parse stored content
 proc recall {name} {
   if {[info exists ::_adp_memory($name)]} {
     set parsecommand [list ns_adp_parse -string]
     lappend parsecommand $::_adp_memory($name)
     ns_adp_puts -nonewline [uplevel $parsecommand]
   } else {
     ns_log Error "[ns_adp_argv 0]: Unable to recall"
   }
 }

If the preceding Tcl has been executed (perhaps during server startup), then the following ADP fragment displays the results of a database query in a table, or shows "No rows in result." if there are no rows:

 <%
  set rows {}
  set db [ns_db gethandle]
  ns_db exec "select somecolumn from sometable"
  set row [ns_db bindargs $db]
  while {[ns_db getrow $db $row]}  {
      lappend rows [ns_set get $row "somecolumn"]
  }
  ns_db releasehandle $db
 %>
 
 <remember name="has-rows_header"> <table> </remember>
 <remember name="hasrows_rows"> <tr><td><%=$column%></td></tr> </remember>
 <remember name="hasrows_footer"> </table> </remember>
 No rows in result.
 <remember name="norows">
 
 <%
  if {[llength $rows] > 0}  {
    recall "hasrows_header"
    foreach row $rows {
      set column $row
      recall "hasrows_rows"
    }
    recall "hasrows_footer"
  } else {
    recall "norows"
  }
 %>

In this example, the `remember` procedure stores different parts of the output, and the `recall` procedure retrieves and processes them conditionally based on whether there are rows from the database query.

See Also

ns_adp, ns_adp_include, ns_adp_parse, ns_adp_tagset, ns_register, ns_set

Keywords

ADP, custom tags, server built-in, tag, tagset