Developing Zen Applications
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
Important:
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:
-
A web application of this name is automatically created each time you create a new Caché namespace for any purpose.
-
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.
-
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:
-
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:
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.
Important:
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.
-
-
-
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.
Note:
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.
A Zen application class is a class derived from
%ZEN.application 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.
Application Class Elements
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);
}
}
}
-
The parameter USERPACKAGES is set as follows:
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:
-
XData Contents places the searchForm component on the page as follows:
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:
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:
-
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>
}
}
-
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>
}
}
-
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);
}
}