Skip to main content
Previous sectionNext section

Building Zen Report Classes

The “Zen Report Tutorial” section of the chapter “Introducing Zen Reports” explains that a Zen report is a class that extends %ZEN.Report.reportPage. The “Zen Report Tutorial” explores the structure of a Zen report class by building it in gradual steps.

Other chapters explain how to write the XData ReportDescription and XData ReportDisplay blocks that cause the Zen report class to generate report output in XHTML and PDF formats. These chapters are “Gathering Zen Report Data,” “Formatting Zen Report Pages,” and “Displaying Zen Report Data.”

Building on the foundation established by these prior chapters, this chapter explores Zen report class structure and organization in greater detail. Topics include:

Controlling Zen Reports with Parameters

The word parameter is widely used. This section describes several kinds of parameter that you can use to change the way a Zen report class operates. In each case, the item is called a parameter when it is used to control the Zen report, but in each case, the context and syntax for using the parameter are different.

Class Parameters

A class parameter is an ObjectScript convention that you can use in Zen report classes. For an overview, see “Class Parameters” in the “Caché Classes” chapter of Using Caché Objects.

The “Zen Report Tutorial” section of the chapter “Introducing Zen Reports” introduced the class parameters APPLICATION, DEFAULTMODE, and REPORTXMLNAMESPACE, which the Zen Report Wizard automatically provides when you create a new Zen report class in Studio. A Zen report class supports many additional class parameters. For details, see the following sections in the appendix “Zen Report Class Parameters”:

  • Class Parameters for General Use” provide the general processing instructions for a Zen report.

  • Class Parameters for XSLT Stylesheets” contribute additional, specialized XSLT processing instructions. This set of parameters addresses problems that can occur when the browser is Internet Explorer and the Zen report class is marked as private by setting its CSP class parameter PRIVATE to 1 (True). If this is not your situation, you do not need these additional class parameters.

SQL Query Parameters

When you supply the SQL query that populates your Zen report with data, this query can include SQL parameters, which Zen reports support in the same way as Zen pages. The following is an example of an SQL query that accepts parameters. The ? character is the placeholder for parameters in the query:

SELECT ID,Customer,Num,SalesRep,SaleDate
    FROM ZENApp_Report.Invoice
    WHERE (Month(SaleDate) = ?) OR (? IS NULL)
    ORDER BY SalesRep,SaleDate

The following is an example of a Zen report that provides its data using the sql attribute with <parameter> elements to supply values to the ? placeholders:

Note:

Each ? placeholder in the query requires its own <parameter> element.

<report
  xmlns="http://www.intersystems.com/zen/report/definition"
  name="myReport"
  sql="SELECT ID,Customer,Num,SalesRep,SaleDate
    FROM ZENApp_Report.Invoice
    WHERE (Month(SaleDate) = ?) OR (? IS NULL)
    ORDER BY SalesRep,SaleDate"
    orderby="SalesRep,Customer" >
    <!-- Supply values to the ? query parameters here -->
    <parameter expression='..Month'/>
    <parameter expression='..Month'/>
  <!-- Other report contents appear here -->
</report>
Copy code to clipboard

For detailed information about SQL queries and their parameters, see the section “Building the <report> or <group> Query” in the chapter “Gathering Zen Report Data.”

Data Type Parameters

A data type parameter is an ObjectScript convention that you can use in Zen report classes. For an overview, see “Parameters” in the “Data Types” chapter of Using Caché Objects.

The most useful data type parameter for Zen reports is ZENURL. If a developer assigns the ZENURL parameter to a Zen report class property, this enables a user to set the value of that property at runtime by providing a query parameter in the URI when invoking the Zen report. The ZENURL value names the URI query parameter. Note that by convention, ZENURL values starting with dollar sign (“$”) are reserved for predefined URI query parameters such as $MODE. For example:

Property employeeID As %ZEN.Datatype.string(ZENURL="ID");
Copy code to clipboard

For details, see “Setting Zen Report Class Properties from the URI” in the chapter “Running Zen Reports.”

XSLT Stylesheet Parameters

It is possible for a user to pass XSLT stylesheet parameter values to Zen reports via URI query parameters when invoking the Zen report class. Parameters defined in this way become XSLT global variables inside the XData ReportDisplay <report> element. This convention requires careful coordination of an <xslt> element within the XData ReportDisplay block with a Zen report class property that has at least one property with the ZENURL data type parameter defined.

For details and a complete example, see “<xslt>” in the chapter “Formatting Zen Report Pages”

URI Query Parameters

In addition to the URI query parameters that you might introduce with ZENURL, Zen reports offer several predefined URI query parameters that you can use at runtime to override the values set by the corresponding Zen report class parameters. By convention, the names of these parameters begin with dollar sign (“$”).

The following example uses the $MODE parameter in the URI string to override any value that might have been set for the DEFAULTMODE parameter in the Zen report class. A value of $MODE=pdf changes the type of output to PDF:

http://localhost:57772/csp/myPath/myApp.myReport.cls?$MODE=pdf

For a summary of available URI parameters and their class parameter equivalents, see “URI Query Parameters for Zen Reports” in the chapter “Running Zen Reports.”

Using Runtime Expressions in Zen Reports

You can use runtime expressions in Zen report class XData ReportDisplay blocks. Simply use the #()# container to hold the expression. This convention allows you to reference the following items only:

  • Properties of the Zen report class.

    The following example uses a runtime expression to assign the value of a class property called username to the field attribute of an <item> in XData ReportDisplay. Inside the #()# container, double dot syntax references the property. This can be any property defined in the class or one of its superclasses:

    <item field="#(..username)#" />
    Copy code to clipboard

    The example above assigns the value of a property to an attribute. You can also assign the value of a property as the contents of an element, such as <p> in the following example. Here the example also shows how you can concatenate the value of a property with regular text using ObjectScript conventions:

    <p>#("The current user is " _ ..username)#</p>
    Copy code to clipboard
  • ObjectScript expressions.

    Runtime expressions can contain ObjectScript expressions, as long as they resolve to a value. The following example uses a runtime expression to assign a calculated time value to the field attribute of an <item> in XData ReportDisplay:

    <item field="#($ZDATETIME($HOROLOG,3))#" />
    Copy code to clipboard

    Similarly, this could work for the contents of an element, such as <p>:

    <p>#($ZDATETIME($HOROLOG,3))#</p>
    Copy code to clipboard
  • The special variable %display.

    %display represents the top level <report> container within XData ReportDisplay. Properties of the %display object correspond to attributes of the <report> element. For example, if you have the <report> title attribute defined as shown here:

    XData ReportDisplay 
      [ XMLNamespace = "http://www.intersystems.com/zen/report/display" ]
    {
      <report xmlns="http://www.intersystems.com/zen/report/display" 
              title='Help Desk Sales Report' style='standard'>
          <!-- OTHER PARTS OF THE REPORT -->
      </report>
    }
    Copy code to clipboard

    Then in other parts of XData ReportDisplay, inside the <report> container, you can use the runtime expression #(%display.title)# to represent the value of the <report> title attribute, for example:

    XData ReportDisplay 
      [ XMLNamespace = "http://www.intersystems.com/zen/report/display" ]
    {
      <report xmlns="http://www.intersystems.com/zen/report/display" 
              title='Help Desk Sales Report' style='standard'>
        <body>
          <header>
            <p class="banner1">#(%display.title)#</p>
          </header>
            <!-- OTHER PARTS OF THE REPORT -->
        </body>
      </report>
    }
    Copy code to clipboard

The section “Organizing Zen Reports to Reuse Code” describes how to use composites and templates to define reusable portions of code that you can reference from XData ReportDisplay in the main Zen report class. Runtime expressions work in composite and template classes. This is because, at runtime, Zen integrates the XData material from composites and templates into the code generated by the XData ReportDisplay that references them. Any runtime expressions found in the composite or template classes become part of the higher level XData ReportDisplay block.

Note:

Runtime expression #()# syntax does not work in XData ReportDefinition blocks.

Many XData ReportDefinition and XData ReportDisplay elements support the use of expressions without the runtime expression #()# container. This is true of:

Localizing Zen Reports

The “Zen Localization” chapter of the book Developing Zen Applications explains how to substitute translated text for different language locales in Zen applications. These concepts apply to Zen reports as well.

Adding Entries to the Message Dictionary

Anywhere that you use the Zen data type %ZEN.Datatype.caption or $$$Text macros in the code for a Zen report, the corresponding text value becomes an entry in the message dictionary for that namespace, from which it can be exported for translation. Later, the translated text can be imported back into the message dictionary to localize the application.

Important:

Localization works only if the DOMAIN parameter is defined as a non-empty string in the Zen report class. Messages for classes in the same localization DOMAIN are stored together in the message dictionary for that namespace.

Many attributes of Zen report components are already of type %ZEN.Datatype.caption and so automatically support localization. Their descriptions in this book indicate when this is the case. You can use $$$Text macros anywhere that ObjectScript is supported, including the values of Zen report runtime expressions. Additionally, Zen report classes offer a shortcut to invoking the $$$Text macros. This shortcut is available only within an XData ReportDisplay block in a Zen report class.

The following syntax:

<item value="@footertime@Created on: " />
Copy code to clipboard

Creates a message dictionary entry for that namespace with:

  • Message text "Created on: " — this is the text to translate for other languages

  • Message identifier "footertime" — this is how the Caché localization facility finds the translated text

Unlike when you use the $$$Text macros, when you use the double-@ shortcut it is your responsibility to ensure that each message identifier ("footertime" in this example) is unique across the localization DOMAIN.

Important:

The double-@ shortcut works only if the special variable %response.Language is set correctly. The way to do this is to place the following statement in the %OnBeforeReport callback method within the Zen report class. This method runs automatically before the report displays:

Method %OnBeforeReport() As %Status
{
  Set %response.Language =
    ##class(%MessageDictionary).MatchLanguage($$$SessionLanguage,"ZenReport")
  Quit $$$OK
}
Copy code to clipboard

Localization for Excel Output

The section “Configuring Zen Reports for Excel Spreadsheet Output” describes how to use Zen reports to create Excel spreadsheets. When you create spreadsheets, the excelName attribute of <element> provides text for column headers in the spreadsheet. When you use a ReportDefinition block that has the required specific structure, excelName supports localization by automatically putting the column header text into the message dictionary. When you use the ReportDisplay block to create an Excel spread sheet from XML in an arbitrary format, you must take some additional steps to localize the column header text.

  1. Such reports require the output mode displayxlsx, so you must set DEFAULTMODE or $MODE appropriately. You must also set the DOMAIN parameter. DOMAIN is an arbitrary text string, used to match entries in the message dictionary with the classes that generated them. Localization does not work if you have not set DOMAIN.

    Parameter DEFAULTMODE As STRING = "displayxlsx";
    Parameter DOMAIN As STRING = "SKISC"; 
    Copy code to clipboard
  2. Create a ReportDefinition block that provides XML for the report, such as the one in the following code sample.

    XData ReportDefinition 
    [ XMLNamespace = "http://www.intersystems.com/zen/report/definition" ]
    {
    <report xmlns="http://www.intersystems.com/zen/report/definition"
     name="MyReport" 
     sql="SELECT TOP 10 Name,DOB,Age FROM Sample.Person" 
     runtimeMode="0">
      <group name="Person">
       <attribute name="name" field="Name"/>
       <attribute name="dob" field="Dob" 
        expression="..ToExcelDate(%val)"/>
       <attribute name="age" field="Age"/>
      </group>
    </report>
    }
    Copy code to clipboard
  3. Define a ReportDisplay block that formats the report for Excel output. The “$$$” at the start of the value of excelName marks that text value for localization.

    XData ReportDisplay 
    [ XMLNamespace ;= "http://www.intersystems.com/zen/report/display" ]
    {
    <report xmlns="http://www.intersystems.com/zen/report/display"
     name="MyReport">
      <body>
       <table group="Person" excelSheetName="Persons" 
        excelGroupName="Person" oldSummary="false">
        <item field="@name" excelName="$$$Name" />
        <item field="@dob" isExcelDate="true" 
         excelName="$$$Date of Birth" />
        <item field="@age" isExcelNumber="true" 
         excelName="$$$Age">
        </item>
       </table>
      </body>
    </report>
    }
    Copy code to clipboard

    The name of the report must be the same in the ReportDefinition and the ReportDisplay.

  4. In order to generate entries in the ^CacheMsg Global for the $$$ expressions, write a helper ClassMethod. It generates the entries during compile of the report:

    ClassMethod Translations() 
    {
        set x=$$$Text("Name")
        set x=$$$Text("Date of Birth")
        set x=$$$Text("Age")
    } 
    Copy code to clipboard
  5. Export CacheMsg:

    DO ##class(%Library.MessageDictionary).ExportDomainList("C:\Temp\local.xml","SKISC") 
    Copy code to clipboard
  6. Translate the exported message file, and save the localized versions.

  7. Import your translation.

    DO ##class(%Library.MessageDictionary).Import("C:\Temp\local_de.xml")
    Copy code to clipboard

Organizing Zen Reports to Reuse Code

In designing a suite of Zen reports, you might discover that you wish to reuse portions of your page design in other reports. Reuse ensures consistency among related reports and generally speeds development. Zen reports offer two ways to achieve reuse within XData ReportDisplay blocks: composites and templates. The primary distinction is that composites can accept parameters at runtime, and templates are entirely static.

The SAMPLES namespace provides a detailed code example in the ZENApp package. The Zen report class ZENApp.CompositeReport.cls defines a report that renders identically to the ZENApp.MyReport.cls example, except that it does so using composites and templates. The ZENApp.CompositeReport package contains several classes that define the composites and templates referenced by ZENApp.CompositeReport.cls.

The following table provides a quick comparison between composites and templates:

Extends XData Block Top Level Container Element For More Information
%ZEN.Report.Display.composite Display <composite> Using Zen Report Composites
%ZEN.Report.Display.reportTemplate (any name) <template> Using Zen Report Templates

Using Zen Report Composites

This topic uses the term composite to describe a block of Zen report syntax that you wish to define separately and then reference repeatedly to provide consistency and reusability for your Zen reports. Like templates, composites can define any part of the XData ReportDisplay block, including elements you would normally place within the <document> <body> or <report> containers. Unlike templates, composites can accept parameters whose values are supplied at runtime. These parameters must be defined as properties of the composite class; you supply values for these properties when you reference the composites in the XData ReportDisplay block of a Zen report class.

The sections in this topic explains all the steps required for this technique to work:

Note:

As long as the set of properties defined in the composite class does not change in any way, a composite class may be modified and recompiled without recompiling the classes that reference the composite. The composite changes are picked up at runtime.

A Zen report composite is a subclass of %ZEN.Report.Display.composite that contains an XData Display block. Unlike templates, the name of the definition block for all composites must be the same: XData Display. Inside the XData Display block is the definition of the composite. This definition is substituted into the code generated by any XData ReportDisplay block that references the composite. In addition to an XData Display block, the composite class can define properties; the purpose of these properties is to allow you to pass values to the composite to modify details of its XData Display definition at runtime.

The sections “Creating a Composite to Define Style” and “Creating a Composite to Define Layout” show the parts of a %ZEN.Report.Display.composite class in more detail.

Creating a Composite to Define Style

The following figure highlights key parts of a %ZEN.Report.Display.composite class whose purpose is to define a set of style classes to place within a <document> container. A detailed description follows this figure.

Composite Class for Zen Report Style
generated description: composite style
  • A composite class must extend the class %ZEN.Report.Display.composite.

  • The class must define the NAMESPACE parameter. This parameter can have any value you wish.

  • The name of the XData block must be Display.

  • The XData Display block must provide an XMLNamespace keyword with the following value:

    http://www.intersystems.com/zen/report/display

  • The top-level container element in XData Display must be a <composite>. It can have an xmlns attribute value that is the same as, or different from, the value of the XMLNamespace keyword.

  • Define a supplementary XML namespace name for the <composite> container using xmlns: attribute syntax, as shown. This new namespace becomes important when you reference the composite from another class. It has the same value you used for the NAMESPACE parameter. In the example, this new namespace is called mystyle.

  • The purpose of the composite class shown in the example is to define a set of styles to place within a <document> container in a Zen report. Over time you might accumulate a large library of style composite classes. To ensure clean naming conventions for managing these classes and references to them, InterSystems recommends that you construct style names based on the simple name of the class itself, as seen in the example (the style for table headers, th.mygroupheader, takes its style name from the class, whose simple name is mygroupheader).

  • After you compile the composite class, its simple class name (mygroupheader in the example) becomes the name of an XML element that you can place in the XData ReportDisplay block of any Zen report class. Doing so references the composite and causes its contents to be substituted at that location in the XData ReportDisplay block. For syntax details, including the correct use of namespaces when you make the reference, see the section “Referencing a Composite from a Zen Report.”

Creating a Composite to Define Layout

The following figure highlights key parts of a %ZEN.Report.Display.composite class whose purpose is to define a <pagefooter> within a <report>. A detailed description follows this figure.

Composite Class for Zen Report Display
generated description: composite display
  • The basic characteristics of base class, name of XData block, value of XMLNamespace keyword, top-level container, and xmlns attribute, are the same as described in the section “Creating a Composite to Define Style.”

  • The purpose of the composite class shown in the example is to define a layout to place at the location within a <report> where you want a <pagefooter> to appear. The composite class offers three properties. All composite class properties must be defined with Zen data types, as shown. If you simply define them, you work with them as attributes when referencing the composite:

    Property username As %ZEN.Datatype.string;
    Copy code to clipboard

    If you assign them an XMLPROJECTION of "element", you work with them as elements when referencing the composite:

    Property username As %ZEN.Datatype.string(XMLPROJECTION = "element");
    Copy code to clipboard

    Regardless of how you project the property, inside the XData Display block in the composite class, each property reference takes this form:

    #(..property_name)#
    Copy code to clipboard

    The #()# syntax for containing this reference is not unique to composites; it indicates that this is a Zen reports expression. For syntax details, see “Using Runtime Expressions in Zen Reports.” Inside the #()# container, double dot syntax refers to a property of this class, where property_name is the name of the property. The complete line that references the username property within the example composite class XData Display block is:

    <item field="#(..username)#" />
    Copy code to clipboard

    When a composite class uses a runtime expression like this, it expects its caller, the Zen report, to send it a value for the username property that works correctly as a value for the <item> field attribute. The next section, “Referencing a Composite from a Zen Report,” shows a correct example of this relationship. If a property value sent to a composite from the Zen report is wrong for that composite, the corresponding section of the report fails.

  • If you need to localize your Zen reports into other languages, you must enable localization for your composite by setting the DOMAIN parameter to a non-empty value and by using localization syntax in text values where appropriate. For details about the double-@ syntax shown in the example, see “Localizing Zen Reports.”

  • After you compile the composite class, its simple class name (ReportFooter in the example) becomes the name of an XML element that you can place in the XData ReportDisplay block of any Zen report class. Doing so references the composite and causes its contents to be substituted at that location in the XData ReportDisplay block. For syntax details, including the correct use of namespaces and how to provide values for composite properties when you make the reference, see the section “Referencing a Composite from a Zen Report.”

Referencing a Composite from a Zen Report

The following figure highlights the XData ReportDisplay block of a Zen report class that references several composites, including those defined in the previous sections, “Creating a Composite to Define Style” and “Creating a Composite to Define Layout.” Following this is a detailed description with numbers to match the figure.

XData ReportDisplay with References to Composites
generated description: composite reference
  • The <report> element must define an XML namespace prefix to match the NAMESPACE parameter value for each composite class that it references. Do this using xmlns: attribute syntax. The example gives style composites the namespace prefix mystyle and layout composites the namespace prefix my. This separation is arbitrary, but makes sense as a way of organizing composite classes.

    The URI values assigned to each namespace prefix must agree with the NAMESPACE and <composite> definitions provided in the corresponding composite classes. Compare the values shown here with the values shown in “Creating a Composite to Define Style” and “Creating a Composite to Define Layout.”

    XData ReportDisplay 
      [ XMLNamespace = "http://www.intersystems.com/zen/report/display" ]
    {
      <report xmlns="http://www.intersystems.com/zen/report/display"
        xmlns:mystyle="http://www.intersystems.com/zen/report/display/my/style"
        xmlns:my="http://www.intersystems.com/zen/report/display/my">
        <!-- CONTENTS OF REPORT HERE -->
      </report>
    }
    Copy code to clipboard
  • When referencing the composite, use its name as an XML element inside XData ReportDisplay. Prefix the composite name with the namespace prefix and a colon character. The following example shows the namespace prefix mystyle and the composite name mygroupheader. To review the syntax in the mygroupheader composite class, see the section “Creating a Composite to Define Style.” This composite has no properties:

    <mystyle:mygroupheader />
    Copy code to clipboard

    This reference causes all the code between <composite> and </composite> in the mygroupheader composite class to be substituted at this location in the XData ReportDisplay block.

  • Here we have the namespace name my and the composite name ReportFooter. To review the syntax in the ReportFooter composite class, see the section “Creating a Composite to Define Layout.” This composite has three properties, class, timestamp, and username.

    <my:ReportFooter class="table.mygroupfooter" 
                     timestamp="@timestamp" 
                     username="@username" />
    Copy code to clipboard

    This reference causes all the code between <composite> and </composite> in the ReportFooter composite class to be substituted at this location in the XData ReportDisplay block. During the ReportFooter substitution, anywhere the class, timestamp, and username properties are referenced in the composite class, Zen substitutes the values that you assigned in XData ReportDisplay.

  • For any properties of the ReportFooter class that are projected as XML attributes, you must provide an attribute of that name and set it to the value you want the property to have. For example:

    <my:ReportFooter class="table.mygroupfooter" 
                     timestamp="@timestamp" 
                     username="@username" />
    Copy code to clipboard

    In this example, the class value is a literal string. Values for timestampand username come from attributes in the XML generated by the XData ReportDefinition block in the same Zen report class, so they use XPath @ syntax. Alternatively, you could use a Zen report runtime expression as the value of any attribute of a composite.

    Composite class properties that you define with Zen data types automatically project themselves to XML as attributes. If you want to project them as XML elements, this choice changes how you define them and how you reference them. In the composite class, instead of this:

    Property username As %ZEN.Datatype.string;
    Copy code to clipboard

    You would do this:

    Property username As %ZEN.Datatype.string(XMLPROJECTION = "element");
    Copy code to clipboard

    The syntax for referencing the composite is different for properties projected as elements. Suppose the properties for ReportFooter are:

    Property class As %ZEN.Datatype.string;
    Property timestamp As %ZEN.Datatype.string;
    Property username As %ZEN.Datatype.string(XMLPROJECTION = "element");
    
    Copy code to clipboard

    Then the reference to the ReportFooter composite in XData ReportDisplay would look like this:

    <my:ReportFooter class="table.mygroupfooter" timestamp="@timestamp">
      <my:username>@username</my:username>
    </my:ReportFooter>
    Copy code to clipboard

Using Zen Report Templates

Note:

The template feature described in this section is not related to XSLT templates. Compare the section “Supplying XSLT Templates to Zen Reports.”

This topic uses the term template to describe a block of Zen report syntax that you wish to define separately and then reference repeatedly to provide consistency and reusability for your Zen reports. Like composites, templates can define any part of the XData ReportDisplay block, including parts of the <document> block and parts of the <body> block. Unlike composites, templates are entirely static; they cannot accept parameters. A template provides simple code substitution.

You can define a display element, such as a <header> or <footer>, as a template. Then you can reference that template when you place the display element inside the <body> block of your XData ReportDisplay definition. The template definition of the element entirely replaces any attributes or other children of the element.

Creating a Zen Report Template

A Zen report template is a subclass of %ZEN.Report.Display.reportTemplate that contains an XData block. Inside the XData block is the template name and definition. The following is an example of a Zen report template class that defines a template called Header1:


Class ZENApp.HeaderTemplate Extends %ZEN.Report.Display.reportTemplate
{
  XData Header1
        [ XMLNamespace = "http://www.intersystems.com/zen/report/display" ]
  {
    <template>
      <header>
        <p class="banner1">HelpDesk Sales1 Report</p>
        <fo><line pattern="empty"/><line pattern="empty"/></fo>
        <table orient="row" width="3.45in" class='table1'>
          <item value="Sales by Sales Rep" width="2in">
            <caption value="Title:" width="1.35in"/>
          </item>
          <item field="@month" caption="Month:"/>
          <item field="@author" caption="Author:"/>
          <item field="@runBy" caption="Prepared By:"/>
          <item field="@runTime" caption="Time:"/>
        </table>
      </header>
    </template>
  }
} 
Copy code to clipboard

Templates are only instantiated at runtime. You may use runtime expressions and data items in defining a template; however, any runtime expressions or data items used in a template are evaluated in the context of the report or composite that invokes the template, not the template class itself. The previous template example works only if the data items that it refers to (@month, @author, @runBy, and @runTime) actually exist in the XML data generated by corresponding XData ReportDefinition block.

Referencing a Zen Report Template

The following syntax example references the sample template from the previous section:

<header template="ZENApp.HeaderTemplate:Header1" />
Copy code to clipboard

Each element that contributes visible content to the report display — that is, each element that may appear within a <body> block — supports the template attribute. To see a list of display elements, refer to the <report> and <body> sections in the chapter “Formatting Zen Report Pages.” The following table describes the template attribute that all of these elements support.

Attribute Meaning
template
Specifies the template that can be used to specify this element, rather than providing attributes and other content directly in XData ReportDisplay. The format for the template value is:
templateClass:templateName
Where:
  • templateClass is the name of the subclass of %ZEN.Report.Display.reportTemplate that defines the template, for example:
    ZENApp.HeaderTemplate
  • templateName is the name of the specific XData block within the templateClass that provides the template for this element, for example:
    Header1

Generally, when you reference a template, you do not provide any attributes other than template with the element, because Zen ignores the values of any other attributes supplied along with the template attribute. Suppose the <header> element in the previous example also provided a width attribute, as follows:

<header template="ZENApp.HeaderTemplate:Header1" width="7.25in" />
Copy code to clipboard

Zen would ignore the value of this width attribute, and instead use whatever width characteristics it found in the Header1 template definition. If it found no width value in the template, it would use the Zen defaults for width, rather than the width attribute provided with this <header> element.

The following sample XData ReportDisplay block shows this sample <header> element in its full context. This example includes a <document> element because the desired output format is PDF:


XData ReportDisplay
      [ XMLNamespace = "http://www.intersystems.com/zen/report/display" ]
{
  <report xmlns="http://www.intersystems.com/zen/report/display"
          name='myReport' title='HelpDesk Sales Report' style='standard'>
    <document width="8.5in" height="11in"
              marginLeft="1.25in" marginRight="1.25in"
              marginTop="1.0in" marginBottom="1.0in"
              referenceOrientation="0">
    </document>
    <body>
      <header template="ZENApp.HeaderTemplate:Header1"/>
      <!-- OTHER ELEMENTS OF THE REPORT DISPLAY -->
    </body>
  </report>
}
Copy code to clipboard

Supplying XSLT Templates to Zen Reports

Note:

The features described in this section are specifically related to XSLT templates. For a more general feature that allows you to reuse sections of Zen report syntax in various reports, see “Using Zen Report Templates” in “Building Zen Report Classes.”

If you are already familiar with XSLT, you might have used <xsl:call-template> syntax to invoke a specific template within an XSLT transformation. <xsl:call-template> permits you to supply parameters and values to the template when you invoke it. Display elements support a similar convention.

In addition to the XData blocks called ReportDefinition and ReportDisplay, Zen report classes support three XData blocks that can contain XSLT templates. The following table lists them.

Use this case-sensitive XData block name… For XSLT templates that apply to…
XData HtmlXslt All XHTML output
XData XslFoXslt All XSL-FO for PDF output
XData AllXslt All output

If you need to use more than one <xsl:template>, you can enclose them in an <zenxslt> element. See the section XData Blocks for <xslt> for more information on <zenxslt>. XData blocks that contain XSLT templates can be in the same report class where they are used, or in a separate report class, where they can be used by a number of reports.

In the following excerpt from a Zen report class, the XSLT templates are empty, so they do not accomplish anything. The example simply shows how to place an XSLT template inside an XData block. You can place any appropriate XSLT statements inside the <xsl:template> container:

XData HtmlXslt {
  <xsl:template name="htmExample" >
  </xsl:template>
}
XData XslFoXslt {
  <xsl:template name="pdfExample" >
  </xsl:template>
}
XData AllXslt {
  <xsl:template name="allExample" >
  </xsl:template>
}
Copy code to clipboard

The name provided for the <xsl:template> container in XData HtmlXslt and XData XslFoXslt can be the same name, because these two XData blocks are never used together. If there is an XSLT template that you need for both output formats, place it in XData AllXslt.

Calling XSLT Templates to Apply Styles

One common use for XSLT templates is to call a template to apply styles. The appropriate XSLT template to call depends on the output format. Define a template with the same name in both of the two output-specific blocks:

  • For XHTML, name the block XData HtmlXslt

  • For PDF, name the block XData XslFoXslt

For example:

XData HtmlXslt
{
  <xsl:template name="redeven" >
    <xsl:param name="num"/>
    <xsl:if test="$num mod 2 = 0">
      <xsl:attribute name='style'>color:red</xsl:attribute>
    </xsl:if>
  </xsl:template>
}
Copy code to clipboard
XData XslFoXslt
{
  <xsl:template name="redeven" >
    <xsl:param name="num"/>
    <xsl:if test="$num mod 2 = 0">
      <xsl:attribute name='color'>red</xsl:attribute>
    </xsl:if>
  </xsl:template>
}
Copy code to clipboard

With these templates defined, it is possible for the XData ReportDisplay block in the same Zen report class to have an <item> defined as follows:

<item field="@id" width=".7in" 
 style="border:none;padding-right:4px"
 stylecall="redeven" styleparams="@id" 
 styleparamNames="num" > </item>
Copy code to clipboard

Where:

  • The stylecall attribute identifies the <xsl:template> name.

  • The styleparamNames attribute identifies the <xsl:param> name as defined in the <xsl:template>. Additional names may appear, separated by semicolons.

  • The styleparams attribute provides an expression that identifies the value to assign to that parameter. A styleparams expression can be a literal value, node set, XPath expression, or XSLT function call. Anything that is valid as a value for <xsl:with-param> in XSLT is valid in styleparams. More than one expression may appear, separated by semicolons. The number of styleparamNames and styleparams must match.

The result is that entries with even numbered IDs are colored red. See the table of attributes in the section “Report Display Attributes” for more information on these attributes.

Calling XSLT Templates While Rendering Items

Note:

The feature described in this section applies only to the <item> element.

To define the XSLT template, place it within an XData block called AllXslt, HtmlXslt, or XslFoXslt in the Zen report class. For example:

XData AllXslt
{
<xsl:template name="sum">
  <!-- Initialize nodes to empty node set -->
  <xsl:param name="nodes"/>
  <xsl:param name="result" select="0"/>
  <xsl:choose>
    <xsl:when test="not($nodes)">
      <xsl:value-of select="$result"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:variable name="value" select="$nodes[1]"/>
       <!-- recursively sum the rest of the nodes -->
      <xsl:call-template name="sum">
        <xsl:with-param name="nodes" select="$nodes[position( ) != 1]"/>
        <xsl:with-param name="result" select="$result + $value"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
}
Copy code to clipboard

With this template defined, it is possible for the XData ReportDisplay block in the same Zen report class to have an <item> defined as follows:

<item call="sum" params="Sale/@amount" paramNames="nodes">
  <caption value="Total Sales"/>
</item>
Copy code to clipboard

Where:

  • The call attribute identifies the <xsl:template> name.

  • The paramNames attribute identifies the <xsl:param> name as defined in the <xsl:template>. Additional names may appear, separated by semicolons.

  • The params attribute provides an expression that identifies the value to assign to that parameter. A params expression can be a literal value, node set, XPath expression, or XSLT function call. Anything that is valid as a value for <xsl:with-param> in XSLT is valid in params. More than one expression may appear, separated by semicolons. The number of paramNames and params must match.

When you obtain the value for the <item> using call, you cannot use the formatNumber attribute to format the result inside the <item> statement. Instead, use the XSLT format-number function inside the <xsl:template> that you are referencing with the call attribute. The following example of an XData AllXslt block shows this convention:


XData AllXslt
{
<xsl:template name="mypct">
    <xsl:param name="num"/>
    <xsl:param name="denom"/>
    <xsl:variable name="v1" select="$num[1]"/>
    <xsl:variable name="v2" select="$denom[1]"/>
    <xsl:choose>
        <xsl:when test="$v2 != 0">
            <xsl:value-of 
    select="format-number(round(($v1 div $v2)*10000) div 10000,'##,###.00%')"/>
        </xsl:when>
        <xsl:otherwise>
             <xsl:value-of select="format-number(0,'##,###.00%')"/>
         </xsl:otherwise>
     </xsl:choose>
</xsl:template>
}
Copy code to clipboard

The properties call, paramNames, and params are supported only by the <item> element. See the table of attributes in the section “<item>” for more information on these attributes.

Conditionally Executing Methods in Zen Reports

As described in the section “Zen Report Tutorial,” a Zen report class is also a CSP page. This means that, at runtime, portions of its logic may execute on the server and portions in the browser, as is normal for CSP pages.

By default, Zen report classes process XSLT and generate XHTML output on the server, then ship the results to the browser for display. This behavior is controlled by the XSLTMODE parameter, which is set to "server" by default. You can set XSLTMODE to "browser" if you prefer, but note that when the output format is PDF, processing happens on the server side regardless of the XSLTMODE setting.

When the output format is XHTML and XSLTMODE is set to "browser", the CSP instance for a Zen report communicates twice with the browser, once to generate XML and a second time to generate XSLT. As a result, methods that fit the following description are automatically invoked twice each time you display the Zen report:

  • Any callback methods, such as %OnPreHTTP() or %OnAfterReport(), that contain custom code in the Zen report class.

  • Any methods invoked by the InitialExpression for any class properties.

To prevent needless performance cost when XSLTMODE is set to "browser", you can program these methods so that parts of them execute conditionally based on the current display mode of the Zen report. The following example accomplishes this for the callback method %OnAfterReport(). This example uses a variety of conventions to check the current display mode, as follows:

  • The ObjectScript function $ZCONVERT ($ZCVT) with the option "L" converts the characters in the given string to all lowercase.

  • The ObjectScript function $GET ($G) returns the data value of the specified variable, or the default for this variable if no value has been set.

  • %request.Data syntax retrieves the value of the $MODE parameter from the URL string that was supplied to invoke the CSP page. See the section “%CSP.Request Object” in the book Using Caché Server Pages (CSP).

  • The macro $$$GETPARAMETER("DEFAULTMODE") looks up the value of the DEFAULTMODE parameter from the Zen report. If DEFAULTMODE is not defined there, the macro looks up the value from the Application class. The application class is either specified in the APPLICATION parameter, or is %ZEN.Report.defaultApplication.

Method %OnAfterReport() As %Status
{
 if $IsObject($G(%request))
  {
   set tMode = $ZCVT($G(%request.Data("$MODE",1),$$$GETPARAMETER("DEFAULTMODE")),"L")
   if (tMode="tohtml")
   {
    ; Place the callback logic for XSLT generation here
   }
   elseif (tMode="html")
   {
    ; Place the callback logic for XHTML generation here
   }
  }
  else
  {
   ; Place the callback logic for call from command line here
  }
  Quit $$$OK
}
Copy code to clipboard

The following example shows a property with an InitialExpression that is set by calling a method:

Property testProp As %String [ InitialExpression = {..initExpr()} ];
Copy code to clipboard

The method itself could be defined as follows:

Method initExpr() As %String
{
 if $IsObject($G(%request))
 {
  Set tMode = $ZCVT($G(%request.Data("$MODE",1), $$$GETPARAMETER("DEFAULTMODE") ),"L")
  if (tMode="xml")
  { 
   set initVal="mode is xml"
  }
  elseif (tMode="tohtml")
  { 
   set initVal="mode is toHtml"
  }
  elseif (tMode="html")
  { 
   set initVal="mode is html"
  } 
  else
   {
    set initVal="all other modes, for completeness"
   }
 }
 else
 {
  Set initVal="called from command line, no potential second round-trip."
 }
 Quit initVal
}
Copy code to clipboard

Executing Code Before or After Report Generation

A Zen report is a class that extends %ZEN.Report.reportPage. This base class offers callback methods that you can override in your own Zen report class. Use these callback methods to add any statements that you want Zen to execute before or after it receives the initial HTTP request, generates the XML data source, writes the XSLT stylesheets, creates the report display, or outputs the report in the requested format.

The Zen report callback methods execute automatically as explained in the following table:

Callback Methods in Zen Report Classes
Method Executes Purpose Returns
%OnPreHTTP() After the Zen report receives the initial HTTP request and before %OnBeforeReport(). Execute statements based on the data in the initial HTTP request. If your %OnPreHTTP() method returns 0 (false) report execution stops immediately. %Status
%OnBeforeReport() After %OnPreHTTP() and before Zen begins its main processing sequence to generate the XML data source, write the XSLT stylesheets, and create the report display. Adjust input prior to the main processing sequence. %Status
OnAfterCreateDisplay() Near the end of the main processing sequence, after Zen creates the report display object but before it outputs the display object in the requested format.
Adjust display contents prior to output. You can access individual items using their id attributes with %GetComponentByID(id). For an example, see “The id Attribute” in the chapter “Formatting Zen Report Pages.”
%OnAfterReport() After Zen completes all report processing and has output the display in the requested format. Clean up after the main processing sequence. %Status