Zen Applications
A Zen application specifies the full set of activities that can result when a Zen client and server interact. These activities may consist of displaying web pages, issuing queries to a database, writing data to disk, and more. When you create a Zen application, you create a suite of Zen classes that specify these activities. The language you use for these classes is ObjectScript — with embedded snippets of XML, HTML, SVG, and JavaScript as needed.
This chapter explores Zen application programming based on the foundation provided by Using Zen. It provides the following topics:
Zen Classes as CSP Classes
The base application class %ZEN.applicationOpens in a new tab and the base page class %ZEN.Component.pageOpens in a new tab each inherit from %CSP.page. This means that every Zen application class and every Zen page class is also an instantiable Caché Server Page. All the CSP page class properties, methods, parameters, and variables (such as %session) are available to Zen application and page classes.
Experienced CSP programmers should note there are important differences between Zen and CSP classes. For example, the allowed expressions that can appear within #()# syntax is much more restrictive in Zen than it is in CSP. For details, see the section “Zen Runtime Expressions.”
Zen Application Configuration
A Zen application may require configuration of its Caché system settings. In order to accomplish this, you need to configure the web application associated with the Zen application. This section explains what this means, and how to perform the configuration steps.
Zen application code resides in a Caché namespace. Each Caché namespace has at least one web application mapped to it. By default, when you create a new namespace called myNamespace it comes with a web application already mapped to it. This web application has default settings already in place, including the following settings which are significant for Zen:
-
Web application Name — The application name. The default value is:
/csp/myNamespace
A web application of this name is automatically created each time you create a new Caché namespace for any purpose.
-
CSP Files Physical Path — The location on the server where the application stores its files. The default value is:
install-dir/CSP/myNamespace
Where install-dir is the Caché installation directory.
At compile time, a Zen class generates JavaScript (.js) and Cascading Style Sheet (.css) files. It may also include external .js or .css files at compile time. If the Zen class is a custom component, it uses the INCLUDEFILES class parameter to list external files, and if it is an application or page, it uses the CSSINCLUDES or JSINCLUDES class parameters to do so. To specify the files, you can use full URLs or simple file names. In the latter case, Zen assumes that the path to these files is the CSP Files Physical Path for the default web application for the Caché namespace.
Usually a Zen application uses all default settings for its Caché namespace and associated web application. This means that usually you do not need to configure the CSP Files Physical Path. Everything works without your intervention. However, if you want to find the generated files, or supply external .js or .css files to use with your Zen application, then you need to know what the path is: It is the CSP Files Physical Path.
-
Default Timeout — Number of seconds of inactivity that cause the Zen application to time out.
-
Serve Files — For Zen applications, InterSystems strongly recommends that you set this value to “Always” or “Always and cached.” Do not set it to “No.”
If you do not wish to serve files from Caché, you must ensure there are copies of all the Zen static files in each directory that is using Zen. The Zen static files are found in the /csp/broker directory under the Caché installation directory. Place a copy of each of these files on your web server in the directory associated with each Zen application. If you are serving files from Caché, you do not need to do this.
-
Custom Error Page — The page that is displayed when the Zen application encounters an error that it cannot resolve, such as a page missing from the application. By default, this field is blank and the default error page for the browser displays. When a value is supplied for this field, it must be a URL in the appropriate format; that is, it must be the Web application Name followed by the package and class name of the Zen page to display, for example:
/csp/myNamespace/MyDemo.Error.csp
-
Login Page — The URL of the page to use as the application login page. This URL must begin with the Web application Name followed by the package and class name of the Zen page to display, for example:
/csp/myNamespace/MyDemo.Login.csp
The Change Password Page is similar to Login Page, but specifies the page that allows a user to change his or her password. For a discussion and examples, see “Controlling Access to Applications” in the chapter “Zen Security.”
At compile time, a Zen application finds its associated web application and applies its configuration settings. That is, Zen cascades through the following sequence and returns the first web application that it finds:
-
The Zen application knows which Caché namespace it resides in, for example myNamespace.
-
If there is a web application in this namespace whose name is /csp/myNamespace, Zen uses the settings from this web application.
-
If not, Zen searches for any web application mapped to myNamespace and returns one based on standard Caché global collation order. For example, if there are web applications /A and /X that map to myNamespace, Zen uses the settings from application /A.
There is no way to configure an explicit association between a Zen application and a web application. The simplest way to manage the implicit association is to keep each Zen application in its own Caché namespace that has no other application in it and, when you need to configure application settings, configure the default web application for that namespace.
Whether or not you actually need to configure web application settings depends on the features that you add to your Zen application. The beginning of this section lists the settings you most often need to adjust. When you need to configure web application settings, the basic procedure is as follows:
-
Start the Management Portal.
-
Navigate to the Web Applications page (System Administration > Security > Applications > Web Applications).
-
Find the appropriate web application in the list and click its Edit button. The Edit Web Application page displays.
-
Make changes as needed, and click Save.
For High Availability solutions running over CSP, InterSystems recommends that you use a hardware load balancer for load balancing and failover. InterSystems requires that you enable sticky session support in the load balancer. This guarantees that — once a session has been established between a given instance of the gateway and a given application server — all subsequent requests from that user run on the same pair.
This configuration assures that the session ID and server-side session context are always in sync; otherwise, it is possible that a session is created on one server but the next request from that user runs on a different system where the session is not present, which results in runtime errors (especially with hyperevents, which require the session key to decrypt the request). To enable sticky session support, see your load balancer documentation.
It is possible to configure a system to work without sticky sessions, but this requires that the CSP session global be mapped across all systems in the enterprise and can result in significant lock contention so it is not recommended.
Zen Application Classes
A Zen application class is a class derived from %ZEN.applicationOpens in a new tab that provides high-level housekeeping and acts as the single root for all of the pages in the application. The application class does not keep an inventory of its associated pages. The association between the application and its pages occurs because every Zen page class has an optional parameter called APPLICATION that identifies the application class. This parameter is optional because an application class is optional. Your Zen application needs to provide an application class only if you consider it a useful convenience. However, if you do provide an application class, you may provide only one application class and every page in your application must identify this application using the APPLICATION parameter.
The following table lists the elements of a Zen application class.
Element | Role | Specific Items | Description |
---|---|---|---|
Class parameters | General application settings | APPLICATIONNAME | Defines a text string that you can use in titles or labels. |
CSSINCLUDES | Comma-separated list of Cascading Style Sheet (.css) files to include for every page in the application. You can use URLs or simple file names. If you use simple file names, the CSP Files Physical Path specifies the physical path to these files. For further information, see the section “Zen Application Configuration.” For information about style sheets, see the “Zen Style” chapter in the book Using Zen. | ||
HOMEPAGE |
URI of a default Home Page for the application. This makes it possible for the application class to be specified as a starting point for the application, even though it does not itself display a page. When the application class receives an HTTP request, it redirects the request to the URI specified by the HOMEPAGE class parameter. |
||
JSINCLUDES | Comma-separated list of JavaScript (.js) include files to include for every page in the application. You can use URLs or simple file names. If you use simple file names, the CSP Files Physical Path specifies the physical path to these files. For further information, see the section “Zen Application Configuration.” | ||
USERPACKAGES | Comma-separated list of user-defined class packages whose HTML class and style definitions are in pre-generated include files. These include files are available to every page within the application. | ||
USERSVGPACKAGES | Comma-separated list of user-defined class packages whose SVG class and style definitions are in pre-generated include files. These include files are available to every page within the application. | ||
An embedded XML document | CSS style definitions | XData Style | Any CSS style definitions that appear within this XData block are placed on every page within the application. See the “Zen Style” chapter in the book Using Zen. |
When programming, be aware that the application class can contain server-side methods written in ObjectScript, Caché Basic, or Caché MVBasic only. It cannot contain any client-side methods marked with the javascript keyword, or client/server methods marked with the ZenMethod keywords. The application class can execute methods on the server only.
Sample Development Project
The chapter “Zen Layout” in the book Using Zen explains how to use template pages and panes to organize layout and style for a Zen application. This topic traces an example that uses composites and dynamic page generation to organize behavior for a Zen application. This example uses Zen to add new order entry module pages to an existing Weblink application. Constraints are as follows:
-
You need to be able to call the new pages from within your existing application.
-
You do not have the luxury of rewriting the whole application, so you want to evolve into Zen one module at a time.
-
Your order entry page needs to have flexible content. That is, you need to be able to easily update portions of the page depending on the context.
-
Your order entry page is so big and complex, you want to only build what you need when the page is first loaded and then fill in some empty segments on the fly.
-
Ideally, you want to break up the page into segments that can be written simultaneously by different developers.
The following example addresses these constraints. It purposely does not address layout or style but focuses on behavior.
-
The following class provides the main page. It defines a set of named groups. Each group is populated with components from the server in response to user events. The main page also defines an API (set of methods) that the various components can call to work with the page. For example, there is a method that loads a new component into one of the groups on the page. Note the parameter USERPACKAGES, the groups g1 and g2, and the method loadForm, which adds a specific type of form to the page based on the parameters provided to it.
-
The contents of each group is defined using a composite component. A composite is a custom component that you build by grouping one or more built-in Zen components in a specific way. For full details, see the “Composite Components” section in the chapter “Custom Components.” Composites can be developed individually by different developers; this satisfies the project constraints. In the sample code, there are four such composites: searchForm is the starting form that drives the loading of other forms. formOne, formTwo, and formThree are sample forms.
Class TestMe.MainPage Extends %ZEN.Component.page
{
/// Comma-separated list of User class packages whose HTML class
/// and style definitions are in pre-generated include files.
Parameter USERPACKAGES = "TestMe.Components";
/// Class name of application this page belongs to.
Parameter APPLICATION;
/// Displayed name of this page.
Parameter PAGENAME;
/// Domain used for localization.
Parameter DOMAIN;
/// This Style block contains page-specific CSS style definitions.
XData Style
{
<style type="text/css">
/* style for title bar */
#title {
background: #C5D6D6;
color: black;
font-family: Verdana;
font-size: 1.5em;
font-weight: bold;
padding: 5px;
border-bottom: 1px solid black;
text-align: center;
}
/* container style for group g1 */
#g1 {
border: 1px solid black;
background: #DDEEFF;
}
/* container style for group g2 */
#g2 {
border: 1px solid black;
background: #FFEEDD;
}
</style>
}
/// This XML block defines the contents of this page.
XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ]
{
<page xmlns="http://www.intersystems.com/zen"
xmlns:testme="TestMe"
title="">
<html id="title">TestMe Test Page</html>
<vgroup id="gSearch" width="100%">
<testme:searchForm/>
</vgroup>
<spacer height="40"/>
<!-- these groups are containers for dynamically loaded components -->
<hgroup>
<vgroup id="g1"/>
<spacer width="20"/>
<vgroup id="g2"/>
</hgroup>
</page>
}
/// Create a form (via a composite element) and place it into the group with
/// the id groupId. formClass is the name of the composite element
/// containing the form. formId is the id applied to the form.
ClientMethod loadForm(formClass, formId, groupId) [ Language = javascript ]
{
try {
// if there is already a form, get rid of it
var comp = zen(formId);
if (comp) {
zenPage.deleteComponent(comp);
}
var group = zen(groupId);
var form = zenPage.createComponentNS('TestMe',formClass);
form.setProperty('id',formId);
group.addChild(form);
group.refreshContents(true);
}
catch(ex) {
zenExceptionHandler(ex,arguments);
}
}
}
Some important things to notice about the TestMe.MainPage class:
-
The parameter USERPACKAGES is set as follows:
Parameter USERPACKAGES = "TestMe.Components";
This tells the page to use the generated include files for the components in this package. This is important because we are creating objects on the fly, and we want to ensure that the relevant JavaScript objects are available.
-
The XData Contents block for the page provides a reference to the XML namespace for the new composite components:
xmlns:testme="TestMe"
-
XData Contents places the searchForm component on the page as follows:
<testme:searchForm/>
When the user presses one of the buttons on the searchForm, it invokes the loadForm method on the Main page. This deletes the current form (by id) and loads a new form (it does not actually have to be a form, that is just what we have used as an example).
Now take a look at the components for the example. As suggested in “Composite Components,” all of these composite component classes are placed together in their own package, TestMe.Components. The reason for this is that Zen automatically generates JS and CSS header files for components in a given package. By placing these components in a package you can exploit this feature by setting the parameter USERPACKAGES to the package name, on the main page, as shown above:
Parameter USERPACKAGES = "TestMe.Components";
The composites also use their own XML namespace to avoid conflict with other components.
The composite component classes are as follows:
-
searchForm — the starting form that drives the loading of other forms:
Class TestMe.Components.searchForm Extends %ZEN.Component.composite { /// This is the XML namespace for this component. Parameter NAMESPACE = "TestMe"; /// This Style block contains component-specific CSS style definitions. XData Style { <style type="text/css"> </style> } /// Contents of this composite component. XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ] { <composite labelPosition="left"> <button caption="Show FormOne in Group 1" onclick="zenThis.composite.btnForm1();"/> <button caption="Show FormTwo in Group 1" onclick="zenThis.composite.btnForm2();"/> <button caption="Show FormTwo in Group 2" onclick="zenThis.composite.btnForm3();"/> <button caption="Show FormThree in Group 2" onclick="zenThis.composite.btnForm4();"/> </composite> } /// User click on form 1 button. ClientMethod btnForm1() [ Language = javascript ] { // Notify the page that the search button was pressed // and that a new form should be loaded. zenPage.loadForm('formOne','form1','g1'); } /// User click on form 2 button. ClientMethod btnForm2() [ Language = javascript ] { // replace contents of 'g1' with formTwo zenPage.loadForm('formTwo','form1','g1'); } /// User click on form 3 button. ClientMethod btnForm3() [ Language = javascript ] { // replace contents of 'g2' with formTwo zenPage.loadForm('formTwo','form2','g2'); } /// User click on form 4 button. ClientMethod btnForm4() [ Language = javascript ] { // replace contents of 'g2' with formThree zenPage.loadForm('formThree','form2','g2'); } }
Every component that is part of a composite can refer to its containing composite group via its composite property. You can see this in the syntax used to invoke client-side methods defined in the composite class, when the user clicks a button on this form:
onclick="zenThis.composite.btnForm1();"
-
formOne — a sample form:
Class TestMe.Components.formOne Extends %ZEN.Component.composite { /// This is the XML namespace for this component. Parameter NAMESPACE = "TestMe"; /// This Style block contains component-specific CSS style definitions. XData Style { <style type="text/css"> </style> } /// Contents of this composite component. XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ] { <composite labelPosition="left"> <titleBox title="Form One"/> <text id="ctrlF1" label="Search:"/> </composite> } }
-
formTwo — another sample form:
Class TestMe.Components.formTwo Extends %ZEN.Component.composite { /// This is the XML namespace for this component. Parameter NAMESPACE = "TestMe"; /// This Style block contains component-specific CSS style definitions. XData Style { <style type="text/css"> </style> } /// Contents of this composite component. XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ] { <composite labelPosition="left"> <titleBox title="Form Two"/> <text id="ctrlF1" label="F1:"/> <text id="ctrlF2" label="F2:"/> <text id="ctrlF3" label="F3:"/> <text id="ctrlF4" label="F4:"/> </composite> } }
-
formThree — yet another sample form:
Class TestMe.Components.formThree Extends %ZEN.Component.composite { /// This is the XML namespace for this component. Parameter NAMESPACE = "TestMe"; /// This Style block contains component-specific CSS style definitions. XData Style { <style type="text/css"> </style> } /// Contents of this composite component. XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ] { <composite labelPosition="left"> <titleBox title="Form Three"/> <colorPicker onchange="zenThis.composite.colorChange(zenThis.getValue());"/> </composite> } /// colorChange ClientMethod colorChange(color) [ Language = javascript ] { alert('You have selected: ' + color); } }