Tag-based Development with CSP
CSP lets you develop CSP applications using standard HTML files. The CSP compiler converts HTML (and XML) documents into %CSP.PageOpens in a new tab classes that can respond to HTTP requests.
Classes generated by the CSP compiler are no different from, and are completely interoperable with, classes that you create yourself. This gives you the choice between developing CSP pages through HTML pages or through creating classes that are extensions of the %CSP.PageOpens in a new tab, which can be used within applications. Examining the generated CSP classes is often useful in debugging.
The HTML documents processed by the CSP compiler contain tags that might, for example, control class generation, provide control flow, manage data access, and control server-side behavior. These tags are the CSP markup language or CSP tags. These tags are interpreted on the CSP server at development time. The HTML sent to the HTTP client by CSP is completely standard and includes no CSP tags.
Within a CSP file, you can use normal HTML tags plus:
-
Caché data expressions using #( )#, which substitute values at page generation.
-
Caché CSP tags, <csp:xxx>, which provide built-in and custom functionality.
-
Caché scripts, <script language=cache runat=server/compiler>, which execute Caché code during page generation or page compilation.
-
Caché methods: reusable class methods that you can invoke from within a page.
-
Server-side subroutine calls, #server()# and call()#, which invoke server-side subroutines from client-side code (hyperevents).
-
Custom tags, described in the chapter “Developing Custom Tags”
CSP Compiler
The CSP compiler is a set of Caché classes and programs running on a Caché server that
-
Reads and parses an HTML document using the CSP markup language,
-
Applies pattern matching logic based on CSP rules,
-
Generates a Caché class, and
-
Compiles the class into executable code.
For example, when the following simple CSP document, hello.csp is compiled,
<html>
<body>
Hello!
</body>
</html>
the CSP compiler transforms this into a class similar to:
Class csp.hello extends %CSP.Page
{
ClassMethod OnPage() As %Status
{
Write "<html>"
Write "<body>"
Write "Hello!"
Write "</body>"
Write "</html>"
Quit $$$OK
}
}
When a user requests the hello.csp page from a browser, the CSP server invokes the generated OnPage method and the original text of the CSP document is sent to the browser for display.
Automatic and Manual Page Compilation
You can have the CSP server compile CSP source documents into classes either automatically or manually.
If autocompile mode (the default) is on, the CSP server automatically asks the CSP compiler to compile CSP source documents into classes as needed. The CSP server compares the timestamp of the source files with the class timestamp and recompiles any page whose source is newer than its class. Typically this mode is turned off in deployed applications to avoid the overhead of checking the timestamps:
To turn off autocompile,
-
In the Management Portal, navigate to System Administration > Security > Applications > Web Applications.
-
Select an application in the table and click Edit.
-
On the Edit CSP Application page, clear Autocompile.
You can explicitly compile a CSP source file into a class. This is useful for finding errors.
-
Open the CSP source file in Studio.
-
Select Build > Compile.
You can also compile a CSP source file from the Caché command line (the terminal) using the $System.CSP API (as shown in the example). This method loads and compiles the CSP file with the URL path (not physical path) /csp/user/mypage.csp. The c (compile) flag compiles the generated class. The k flag (keep) preserves the generated intermediate code to be viewed.
Do $System.CSP.LoadPage("/csp/user/mypage.csp","ck")
CSP Markup Language
The CSP markup language is a set of directives and tags you can use to control the classes generated by the CSP compiler.
When you compile a CSP document, the result is a Caché class that executes ObjectScript or Basic code. Keep this in mind to help you to develop correct application logic as well as perform troubleshooting. You may, in fact, find examining the code generated by the CSP compiler a useful way to learn more about both CSP and CSP markup language.
It is also important to keep track of what code is executed on the CSP server (as it prepares a response to an HTTP request) and what code is to be executed on the HTTP client (such as HTML and JavaScript).
CSP Page Language
By default, the CSP compiler evaluates runtime expressions and generates code using ObjectScript. For a given CSP document, you can change this default to Basic by putting the PAGE directive at the top of the document:
<%@ page language="Basic" %>
For an example, see the basic.cspOpens in a new tab application included in the CSP samples (click source to view the source).
Within a CSP document, runtime expressions as well as the contents of any server-side <script> tags must use the default language for the page (or else you receive a compile-time error). Or you can define a method in a different language and invoke that from the default language.
Text
Any text contained within a CSP document (HTML or XML) that is not a CSP directive or special tag is sent unchanged to the HTTP client requesting the page.
For example, a CSP document containing the following:
<b>Hello!</b>
generates the following code within the generated class:
Write "<b>Hello!</b>",!
which, in turn, sends the following to the HTTP client:
<b>Hello!</b>
Compile-time Expressions and Code
You can specify that an expression be evaluated at compilation time (as opposed to runtime) of the CSP page. Such expressions are typically used in CSP Rule definitions, though there are times when they may be of use elsewhere.
Compile-time expressions are delimited using the ##(expr)## directive, where expr is an ObjectScript expression.
For example, a CSP document containing the following:
This page was compiled on: <b>##($ZDATETIME($H,3))##</b>
generates the following code in the generated class:
Write "This page was compiled on <b>2000-08-10 10:22:22</b>",!
You can also define lines of code to be executed at page compilation time using the runat attribute of the <script> tag:
<script language="Cache" runat="compiler">
You must write all compile-time expressions and code using ObjectScript.
Runtime Expressions
A CSP document may contain expressions that are run on the CSP server when the page is served (that is, at runtime). Such expressions are delimited using the #(expr)# directive, where expr is a valid ObjectScript or Basic expression (depending on the default language for the page; the language used within a runtime expression must match the default language for the CSP document.)
Note that name indirection is supported and argument indirection is not supported with the #(expr)# directive.
For example, a CSP document containing the following:
Two plus two equals <b>#(2 + 2)#</b>
generates the following code within the generated class:
Write "Two plus two equals <b>", (2 + 2), "</b>",!
which, in turn, sends the following to the HTTP client:
Two plus two equals <b>4</b>
Samples of runtime expressions;
-
Value of a variable set earlier on the page
The answer is <b>#(answer)#</b>.
-
Object property or method
Your current balance is: <b>#(account.Balance)#</b>.
-
Field within a %ResultSetOpens in a new tab object
<table> <csp:while condition="result.Next()"> <tr><td>#(result.Get("BookTitle"))#</td></tr> </csp:while> </table>
-
URL parameter using the %request object
<table bgcolor='#(%request.Data("tablecolor",1))#'></table>
A runtime expression can be anywhere within an CSP document where the #(expr)# structure can be used as legal HTML. This includes within HTML text, as the value of an HTML element attribute, or within the definition of client-side JavaScript code.
If the value of a runtime expression contains any special characters (such as < or > , angle brackets) you need to escape them to send the correct escape sequence to the HTTP client. To escape them, use one of the escape methods provided by the %CSP.PageOpens in a new tab class. See “Escaping and Quoting HTTP Output” for details. The example below shows the EscapeHTML classmethod. Any characters that need to be escaped that exist in object.Description are replaced by their HTML escape sequences when the method is run.
Description: <b>#(..EscapeHTML(object.Description))#</b>.
If a runtime expression is used in an HTML attribute value, any HTML entities found in the runtime expression are converted to the characters that they represent before being converted to executable code. For example:
<font size=#(1 > 0)#>
generates the following code within the generated class:
Write "<font size=",(1 > 0),">",!
Runtime Code
If you need more than a simple expression to run on the CSP server within a page, you can place lines of code to run on the CSP server using the <script runat=server> tag. As with runtime expressions, you can use runtime code for a variety of purposes. The language used for runtime code (specified by the LANGUAGE attribute of the <script> tag) must match the default language for the CSP document.
For example, a CSP document containing the following:
<ul>
<script language="cache" runat=server>
For i = 1:1:4 {
Write "<li>Item ",i,!
}
</script>
</ul>
generates the following code within the generated class:
Write "<ul>",!
For i = 1:1:4 {
Write "<li>Item ",i,!
}
Write "</ul>",!
which, in turn, sends the following to the HTTP client:
<ul>
<li>Item 1
<li>Item 2
<li>Item 3
<li>Item 4
</ul>
Runtime Code ObjectScript Single Line
You can use the following syntax to run a single line of ObjectScript. This works for only a single line. The line cannot wrap.
#[ set x = a + b write x ]#
Server-Side Method
In a CSP document, you can define a method that belongs to the class generated for the document. This is done using arguments with the <script> tag.
You can specify the name of the method as well as its argument list and return type. You can specify the language used to implement the method; this language does not need to match the default language for the CSP document.
For example, the following defines a method called MakeList that creates an ordered list containing count items:
<script language="Cache" method="MakeList"
arguments="count:%Integer" returntype="%String">
New i
Write "<ol>",!
For i = 1:1:count {
Write "<li> Item",i,!
}
Write "</ol>",!
Quit ""
</script>
You can then invoke this method from elsewhere within the CSP document:
<hr>
#(..MakeList(100))#
You can also use inheritance (using the <csp:class> tag) to inherit previously defined methods into your page class or to invoke the class methods of another class:
<hr>
#(##class(MyApp.Utilities).MakeList(100))#
SQL <script> Tag
You can use SQL to define a Caché %ResultSetOpens in a new tab object within a CSP page using the following <script> tag.
The following example creates an instance of a dynamic SQL %ResultSetOpens in a new tab object named query, prepares the specified SQL query, and executes it (gets it ready for iterating over it).
<script language="SQL" name="query">
SELECT Name FROM MyApp.Employee ORDER BY Name
</script>
Typically you use a %ResultSetOpens in a new tab object created by the SQL script tag in conjunction with the <csp:while> tag (see csp:while Tag) to display the results of the query.
The SQL <script> tag closes the instantiated %ResultSetOpens in a new tab object when the page is done executing.
You can specify parameters for the SQL query by using the ? character within the SQL text. You can provide values for parameters using the P1, P2,...Pn attributes of the SQL <script> tag (where n is the number of parameters).
Here is example using the SQL <script> tag to display the purchases made by the current user. (The current user's User ID is assumed to have been previously stored in the %session object):
<script language=SQL name=query P1=#(%session.Data("UserID"))#>
SELECT DateOfPurchase,ItemName,Price
FROM MyApp.Purchases
WHERE UserID = ?
ORDER BY DateOfPurchase
</script>
<hr>
Items purchased by: <b>#(%session.Data("UserID"))#</b>
<br>
<table>
<tr><th>Date</th><th>Item</th><th>Price</th></tr>
<csp:while condition="query.Next()">
<tr>
<td>#(..EscapeHTML(query.GetData(1)))#</td>
<td>#(..EscapeHTML(query.GetData(2)))#</td>
<td>#(..EscapeHTML(query.GetData(3)))#</td>
</tr>
</csp:while>
</table>
Using the <csp:query> tag, you can use a query defined as part of a Caché class to create a %ResultSetOpens in a new tab object:
<csp:query NAME="query" CLASSNAME="Sample.Person" QUERYNAME="ByName">
You can use the resulting %ResultSetOpens in a new tab object in the same way as you would with the SQL<script> tag.
Controlling the Generated Class
Using the <csp:class> tag, you can exert some control over the class generated by the CSP compiler. This control includes selecting superclasses for the class and defining values for many of the %CSP.PageOpens in a new tab class parameters.
For example, suppose that, in addition to inheriting from the usual %CSP.PageOpens in a new tab class, you would like a generated class to also inherit from another class. The SUPER attribute takes a comma-delimited list of classes and uses them as the superclasses for the generated class.
<csp:class SUPER="%CSP.Page,MyApp.Utilities">
Here is an example of redefining the value of a class parameter: To redefine the value of the class parameter PRIVATE as 1 (to define a page as private), use:
<csp:class PRIVATE=1>
Control Flow
The CSP markup language provides several tags to facilitate control over the execution of pages. While not as general purpose as straight server-side tags, these tags can make certain tasks easy to accomplish.
<csp:if> Tag
The <csp:if> tag, along with the <csp:else> and <csp:elseif> tags, provides a way to define conditional output in a CSP page.
The <csp:if> tag has a single attribute, condition, whose value is an ObjectScript or Basic expression (depending on the default language specified for the page) evaluated by the CSP server at runtime. If this value is true, then the contents of the tag are executed.
For example:
<csp:if condition='user="Jack"'>
Welcome Jack!
<csp:elseif condition='user="Jill"'>
Welcome Jill!
<csp:else>
Welcome!
</csp:if>
<csp:while> Tag
The <csp:while> tag provides a way to repeatedly process a section of a CSP document as long as a given server-side condition is true.
The <csp:while> tag's condition attribute contains an ObjectScript or Basic expression (depending on the default language for the page) which is evaluated on the CSP server when a page is served. As long as the condition evaluates to true (1), the content of the csp:while tag is evaluated.
The <csp:while> tag is typically used with a Caché %ResultSetOpens in a new tab object to display the results of an SQL query in HTML. In the example below, the contents of the <csp:while> tag, which writes out the value of the query's Name column, is repeatedly executed until the %ResultSetOpens in a new tab object's Next method returns the value FALSE (0), indicating the end of the result set.
<script language=SQL name=query>
SELECT Name
FROM MyApp.Employee
ORDER BY Name
</script>
<csp:while condition="query.Next()">
#(..EscapeHTML(query.Get("Name")))#<BR>
</csp:while>
Using the <csp:while> tag's counter attribute you can define a counter variable that is initialized to zero (0) and automatically incremented by one (1) at the start of every iteration.
Here, for example, is a way to use the <csp:while> tag to create an HTML table with 5 rows:
<table>
<csp:while counter="row" condition="row<5">
<tr><td>This is row #(row)#.</td></tr>
</csp:while>
</table>
Here is an example of using a not operator (a single quote) in a condition. Note that the condition cannot contain any spaces and does not include start and end quotes. You can also state the condition using parentheses, as (mystr'=”QUIT”).
<csp:while condition=mystr'="QUIT">
//add code
</csp:while>
<csp:loop> Tag: Numbered List Example
The <csp:loop> tag provides another way to repeatedly execute content in a CSP document.
The <csp:loop> tag lets you define a counter variable (using its counter attribute) as well as its starting, ending, and, increment-by value. The default increment-by value is 1.
For example, you can use the <csp:loop> tag to create a list containing 5 items:
<ul>
<csp:loop counter="x" FROM="1" TO="5">
<li>Item #(x)#
</csp:loop>
</ul>
Escaping and Quoting HTTP Output
To create the literal display of special characters used in HTML, you have to use escape sequences. For example, to display the > (right angle bracket) character in HTML, which has a special meaning in HTML, you have to escape it using the sequence of characters:>. Different parts of a CSP document may use different escaping rules (such as HTML and JavaScript).
The %CSP.PageOpens in a new tab class provides a number of escaping and quoting methods:
-
An escaping method takes a string as input and returns a string with all special characters replaced with the appropriate escape sequences.
-
A quoting method takes a string as input and returns a quoted string (that is with the appropriate enclosing quotation marks). The quoted string also has all special characters replaced with escape sequences.
-
For every escaping method, there is a corresponding unescape method that replaces escape sequences with plain text.
Escaping HTML with EscapeHTML
The %CSP.PageOpens in a new tab class can replace characters with their corresponding HTML escape sequences.
For example, if a CSP file needs to display the value of a server-side variable, x, on a browser, any characters within x can be escaped with this expression:
#(..EscapeHTML(x))#
If the value of x is <mytag>, the following escaped text is sent to the HTTP client:
<mytag>
Similarly, escaping is necessary when you send the value of HTML attributes:
<input type="BUTTON" value="#(..EscapeHTML(value))#">
If the value of value is <ABC>, this results in the following text being sent to the HTTP client, where the two angle brackets, left and right, are replaced with their character sequence equivalents: < and > respectively:
<input type="BUTTON" value="<ABC>">
Place "" (quotation marks) around the #()# directive to make the resulting HTML attribute value quoted.
When sending output from a database to an HTTP client, it is good practice to escape it. For example, consider the following expression that writes a username to a web page (assuming user is a reference to an object with a Name property):
User name: #(user.Name)#
If your application lets users enter their names into the database, you may find that a mischievous user may enter a name containing HTML commands. If the following is written out to an HTTP client without HTML escape sequences, the page may have unintended behavior.
Set user.Name = "<input type=button onclick=alert('Ha!');>"
Escaping URL Parameters with EscapeURL
Parameter values in URL strings can also be escaped. URLs use a different set of escape sequences than HTML. The %CSP.PageOpens in a new tab class EscapeURL method replaces all special URL parameter value processing characters with their corresponding escape sequences.
For example, if a CSP file uses the value of a server-side variable, x, as a URL parameter value, any characters within x can be escaped with this expression:
<a href="page2?ZOOM=#(..EscapeURL(x))#">Link</A>
If the value of x is 100%, then the following text is sent to the HTTP client. The % character is escaped as %25.
<a href="page2?ZOOM=100%25">Link</A>
Escaping JavaScript with QuoteJS
The %CSP.PageOpens in a new tab class provides the #(. .QuoteJS(x))# string to replace all special characters with their corresponding JavaScript escape sequences.
For example, suppose a CSP file defines a client-side JavaScript function that displays a message, specified by the value of a server-side variable, x, in an alert box. The value of x is converted into a JavaScript quoted string with:
<script language="JavaScript">
function showMessage()
{
alert(#(..QuoteJS(x))#);
}
</script>
If the value of x is “Don't press this button!”, then the following text is sent to the HTTP client:
<script language="JavaScript">
function showMessage()
{
alert('Don\'t press this button!');
}
</script>
Server-Side Methods
CSP offers two techniques for invoking server-side methods from an HTML client.
-
Using the HTTP Submit mechanism.
-
Using the hyperevents, either #server (synchronous) or #call (asynchronous). You can also use the HyperEventCall() method of %CSP.PageOpens in a new tab; for details and an example, see the class reference.
The advantages of using HTTP Submit are that client-side programming is simple and no client-side components are needed. Its disadvantages are that the page is repainted by the client after a method call and server-side programming is more difficult.
If you use hyperevents, #server and #call are implemented using XMLHttpRequest. #call is asynchronous: if you (as a user) enter a value on a web page, the page is not updated immediately; you may have moved to another page by the time it is updated. #server is synchronous; the page is updated immediately on the return from the call.
Note that synchronous XMLHttpRequest is deprecated by many browsers, and in general, movement is toward only supporting the asynchronous XMLHttpRequest.
HTTP Submit and hyperevents are described further in the sections below.
Caché and AJAX
The acronym AJAX is commonly used to refer to a set of technologies that allows you to update a client-side page's data from the server without having to request a new browser page. Caché hyperlinks allow AJAX interactions without requiring the programmer to handle all the messy communications with the server. Two ways Caché allows AJAX transactions:
-
For CSP the use of the #server() and #call() commands allow direct calling of server-side methods from the client. (You can also use the HyperEventCall() method of %CSP.PageOpens in a new tab; for details and an example, see the class reference.)
-
For Zen, the programmer may define ZenMethods which handle client-server interactions. These may be synchronous or asynchronous depending on the method’s signature:
Signature for a synchronous AJAX request (the method returns a %Status):
Method XYZ(arg) as %Status [ZenMethod]
Signature for an asynchronous AJAX request:
Method XYZ() [ZenMethod]
Parallel Processing with AJAX
AJAX requests to CSP are processed sequentially on the server because of locks on nodes of ^%cspSession global. To enable AJAX requests to be processed in parallel, if the application you are working with does not SET anything in the session global/object (so only reads), you can use the %CSP.Session.Unlock() method to unlock the CSP global for that session and %session.Lock at the end of processing.
For more information see %CSP.SessionOpens in a new tab in the class reference.
Calling Server-side Methods via HTTP Submit
Invoking server code with an HTTP Submit requires very little functionality from the browser. It is a good technique for applications that have a wide audience and must support a wide variety of browsers. When you use an HTTP Submit, the requested page is redisplayed each time that the user clicks a SUBMIT button.
You can handle HTTP submits with the following:
-
Serve an HTML form containing a SUBMIT button:
<form name="MyForm" action="MyPage.csp" method="GET"> User Name: <input type="TEXT" name="USERNAME"><br> <input type="SUBMIT" name="BUTTON1" value="OK"> </form>
This defines a simple form with a text field called USERNAME and a SUBMIT button called BUTTON1. The ACTION attribute of the form specifies the URL to which the form is submitted. The METHOD attribute specifies which HTTP protocol is used to submit the form: POST or GET.
-
When the user clicks BUTTON1, the SUBMIT button, the browser collects the values of all controls in the form and submits them to the URL specified by the form's ACTION attribute. (Note that a page can submit back to itself either by specifying its name with the ACTION attribute or by leaving the ACTION attribute blank.) Regardless of whether a form is submitted via POST or GET, CSP treats the submitted values as if they were URL parameters. In this case, submitting the form is equivalent to requesting the following URL:
MyPage.csp?USERNAME=Elvis&BUTTON1=OK
The name and value of the SUBMIT button is included. If there are multiple SUBMIT buttons on a form, only the data button that was actually pressed is included in the request. This is the key to detecting when a SUBMIT has occurred.
-
The server code, in this case MyPage.csp, detects that a submit has occurred. It does this by testing for the name BUTTON1 in the %request object:
<script language="Cache" runat="SERVER"> // test for a submit button If ($Data(%request.Data("BUTTON1",1))) { // this is a submit; call our method Do ..MyMethod($Get(%request.Data("USERNAME",1))) } </script>
-
After invoking the desired server-side logic, the server code continues and returns HTML for display by the browser. This could be a redisplay of the current form or a different page.
Calling Server-side Methods Using Hyperevents #server and #call
A hyperevent is our CSP extension of a web browser event and a web development technique for creating an interactive web action. Using hyperevents, you can run methods of classes on a Caché server in response to events in a client web browser without reloading the HTML page on the client. This capability is commonly called AJAX in the wider world. Caché hyperevents are useful in a number of cases, especially database applications where you may want to perform validation or search functions against a database without having to reload and reformat an entire web page. Usually, hyperevents are implemented using XMLHttpRequest.
If you are developing CSP pages using classes, you need to call the ..HyperEventHead method during the output of the <head> section to load the JavaScript needed for hyperevents.
Calling Server-side Methods Using #server
Within a CSP file, you can invoke a server-side method using the #server directive. You can use this directive anywhere that JavaScript is allowed.
The syntax of the #server directive is:
#server(classname.methodname(args,...))#
where classname is the name of a server-side Caché class and methodname is the name of a method in the class. args is a list of client-side JavaScript arguments that are passed to the server-side method. Note that all code between the opening and closing #signs must all be on a single line.
For example, to invoke a server-side method named Test in the Caché class MyPackage, use:
<script language="JavaScript">
function test(value)
{
// invoke server-side method Test
#server(MyPackage.Test(value))#;
}
</script>
The CSP compiler replaces each occurrence of #server directive with JavaScript code that invokes the server-side method.
From a given CSP page, you can invoke a method belonging to the class generated for it using ..MethodName syntax. For example:
#server(..MyMethod(arg))#
Calling Server-side Methods Using #call
Synchronicity is determined by the method being called, #server or #call. #server is synchronous; #call is asynchronous.
Synchronous calls may result in a noticeable pause in the UI response (hanging). Asynchronous calls however require their own benefits and problems. For example, if the user enters a value on the web page, the page is not updated immediately; the user may have moved to another page by the time it is updated.
#server is synchronous. When you invoke a server-side method, #server returns a value to the client (since the client is waiting) *and* returns JavaScript for the client to execute
#call is asynchronous: When you invoke a server-side method, #call does not wait for a return value. Instead, your application relies on the JavaScript sent back by the server to perform any needed operations on the client.
When using asynchronous #call, you have to be careful when making multiple, successive calls. If you invoke a method via #call before the previous method is complete, the web server may decide to cancel your previous method call. Or the user might move to another page before the JavaScript executes, so the JavaScript executes on the wrong page, and result in an error. So, mostly, use #call when starting something running that could take a while, and provide a link to another page that shows the status of the long-running job.
As with #server, you can use the #call directive anywhere that JavaScript is allowed.
The syntax of the #call directive is:
#call(classname.methodname(args,...))#
where classname is the name of a server-side Caché class and methodname is the name of a method in the class. args is a list of client-side JavaScript arguments that are passed to the server-side method. Note that all code between the opening and closing #signs must all be on a single line.
For example, to invoke a server-side method named Test in the Caché class MyPackage, use:
<script language="JavaScript">
function test(value)
{
// invoke server-side method Test
#call(MyPackage.Test(value))#;
}
</script>
The CSP compiler replaces each occurrence of the #call directive with JavaScript code that invokes the server-side method.
From a given CSP page, you can invoke a method belonging to the class generated for it using ..MethodName syntax. For example:
#call(..MyMethod(arg))#
Hyperevent Examples
This section shows some examples of hyperevents; that is, where you use the #server and #call directives to perform server actions in response to client events. For instance: you have a form that is used to add a new customer to a database. As soon as the customer name is entered, the application checks to make sure that the customer is not already in the database. The following form definition calls a server-side Find method when the input contents are changed.
<form name="Customer" method="POST">
Customer Name:
<input type="Text" name="CName"
onChange=#server(..Find(document.Customer.CName.value))# >
</form>
In this case, the Find method could be defined in the same CSP file as:
<script language="Cache" method="Find" arguments="name:%String">
// test if customer with name exists
// use embedded SQL query
New id,SQLCODE
&sql(SELECT ID INTO :id FROM MyApp.Customer WHERE Name = :name)
If (SQLCODE = 0) {
// customer was found
// send JavaScript back to client
&js<alert('Customer with name: #(name)# already exists.');>
}
</script>
This method communicates with the client by sending back JavaScript for execution.
Whenever a server-side method is invoked, any output it writes to the principal device is sent back to the client. There, it is converted into a JavaScript function and executed by, and in the context of, the client page.
For example, if a server-side method executes the following lines of code:
Write "CSPPage.document.title = 'New Title';"
Then the following JavaScript is sent to the client and executed:
CSPPage.document.title = 'New Title';
In this case, this changes the title displayed in the browser to New Title. Any valid JavaScript can be sent back to the client in this fashion. Note that you must place a carriage return (using the ! character) at the end of each line of JavaScript or the browser cannot execute it.
To make it easier to return JavaScript from a server method, ObjectScript supports embedded JavaScript using the &js<> directive. This is a special language construct that lets you include lines of JavaScript in an ObjectScript method. When the method containing the embedded JavaScript is compiled, the contents of the &js<> directive is converted to the appropriate Write command statements. Embedded JavaScript can refer to ObjectScript expression using the #()# directive.
For example, a Caché method containing the following:
Set count = 10
&js<
for (var i = 0; i < #(count)#; i++) {
alert('This is pleasing!');
}
>
is equivalent to:
Set count = 10
Write "for (var i = 0; i < ", count, "; i++) {",!
Write " alert('This is pleasing!');",!
Write "}",!
When invoked from a client, this method displays a pleasing alert box 10 times.
Using #server in CSP Classes
To use hyperevents and Javascript within a CSP class, you must call hyperevent broker files explicitly. As in the example below, place #(..HyperEventHead())# just above the closing <head> tag.
Class esl.csptest Extends %CSP.Page [ ProcedureBlock ]
{
ClassMethod OnPage() As %Status
{
&html<<html>
<head>
<script language=javascript>
function onServer()
{
alert(#server(..ServerMethod())#);
}
</script>
#(..HyperEventHead())#
</head>
<body>
<input type=button value="click here" onclick='onServer()' />
</body>
</html>>
Quit $$$OK
}
ClassMethod ServerMethod()
{
quit "from server"
}
}
Tips for Using Server-Side Methods
The ability to invoke server-side methods from a web page is a powerful feature. There are, however, some things that you need to keep in mind when using server-side methods in an application.
Within this section, anything mentioned for #server applies to #call as well unless noted otherwise.
Either the #server or #call directive can call a method on the Caché server from JavaScript in the web browser. This makes CSP able to do things such as validate a field when you move off it rather than waiting for the submission of the form and, so, give the user immediate feedback. There are several factors with using #server syntax that you should be aware of — otherwise it is possible to produce applications that perform very slowly, or, in some cases, do not work at all.
There are two basic rules to keep in mind when using #server:
-
Never use #server in the onload event of a web page. This can fail and it is faster and easier to generate the data when generating the web page in Caché.
-
Do not use #server in the onunload event of a web page. Use as few #server calls as possible and do as much work inside each call as you can because they are expensive, involving a round trip from the browser to the server.
The reason this is not a good idea is because any code that you need to run inside the onload event can be run faster and more easily when the page is generated from Caché. For example, suppose that you wish to setup an initial value for a JavaScript variable that can be used later in the page (maybe in #server calls). So you are currently doing this:
<html>
<head>
<script language="JavaScript">
function LoadEvent()
{
var value=#server(..GetValue())#;
}
</script>
</head>
<body onload=LoadEvent();>
</body>
</html>
<script language="Cache" method="GetValue" returntype="%String">
Quit %session.Data("value")
</script>
However there is absolutely no need to call #server because the value of the JavaScript variable is already known in %session.Data("value") when you are generating the page. Hence, it is much better to write:
<html>
<head>
<script language="JavaScript">
function LoadEvent()
{
var value='#(%session.Data("value"))#';
}
</script>
</head>
<body onload=LoadEvent();>
</body>
</html>
The same trick applies no matter what you are doing, if you are updating the value of a form element when the document loads then change this to put the value in when the page is generated, for example:
<input type="text" name="TextBox" value='#(%request.Get("Value"))#'>
There is never any need to use a #server in the onload event of a page.
Because the page is unloading, it is difficult to know if the JavaScript returned from Caché will be executed or not and the actual behavior depends on the browser. Also, if the user turns off the machine, you never get an onunload event. Your application needs to be able to cope with this possibility in any case, probably by using timeouts on the %session object. You can move the onunload #server logic code to, for example, the start of the next CSP page that the user clicks.
Use as Few #server and #call Calls as Possible
#server and #call work by making the browser issue an HTTP request for a page with a special encrypted token in it that tells Caché the method name to run. Caché runs this method and any output it sends back is executed as JavaScript on the browser, in addition the #server call can also return a value. Because these calls both use an HTTP request, they are both roughly as expensive in network packets, CPU on the server, and so on as a normal CSP page request. If you use a lot of #server requests, then it dramatically reduces the scalability of your application because each #server call is asking for a new CSP page from the Caché server. It means that, instead of a conventional web page where you go to the URL and generate the page once, a CSP page with 10 #server calls in it is as costly as generating ten CSP pages; if you can reduce the number of #server calls you could increase the number of users the application can support by a factor of ten.
The way to reduce the number of #server calls is to make sure that each #server call you use is really needed by the application and, if it is, then make sure that this #server call is doing as much work as possible on the server. For example, below is a block of JavaScript that update a form with some new values from the server.
Note that this block of code uses the CSP keyword CSPPage to refer to the page itself, rather than the Javascript keyword self. In this example, the two keywords work identically. We recommend using CSPPage as self can act unexpectedly in different contexts.
<script language="JavaScript">
function UpdateForm()
{
CSPPage.document.form.Name.value = #server(..workGet("Name",objid))#;
CSPPage.document.form.Address.value = #server(..workGet("Address",objid))#;
CSPPage.document.form.DOB.value = #server(..workGet("DOB",objid))#;
}
</script>
The server code is shown here. (Normally it would use an object or SQL but here we are using a global to keep the code small.)
<script language="Cache" method="workGet"
arguments="type:%String,id:%String" returntype="%String">
Quit $get(^work(id,type))
</script>
This single update is making three calls for new web pages from the Caché server! This can be converted to a single #server call that updates all the values at once, the JavaScript becomes:
<script language="JavaScript">
function UpdateForm()
{
#server(..workGet(objid))#;
}
</script>
The method definition is:
<script language="Cache" method="workGet"
arguments="id:%String" returntype="%String">
&js<CSPPage.document.form.Name.value = #($get(^work("Name",objid)))#;
CSPPage.document.form.Address.value = #($get(^work("Address",objid)))#;
CSPPage.document.form.DOB.value = #($get(^work("DOB",objid)))#;>
</script>
So, instead of multiple calls, you just pass the data once and then make Caché do all the work. If you have a more complex JavaScript example, such as:
<script language="JavaScript">
function UpdateForm()
{
CSPPage.document.form.Name.value = #server(..workGet("Name",objid))#;
if (condition) {
CSPPage.document.form.DOB.value = #server(..workGet("DOB",objid))#;
}
else {
CSPPage.document.form.DOB.value = '';
}
}
</script>
then this should still only ever need one #server call. You just embed the whole if condition into the JavaScript returned by the #server call, so the code workGet method ends up looking like:
<script language="Cache" method="workGet"
arguments="id:%String" returntype="%String">
&js<CSPPage.document.form.Name.value = #(^work("Name",objid))#;
if (condition) {
CSPPage.document.form.DOB.value = #(^work("DOB",objid))#;
}
else {
CSPPage.document.form.DOB.value = '';
}
>
</script>
Creating Custom HyperEvent Error Handler for #server and #call
If you call something with a hyperevent (#server or #call) and, on execution, it fails to communicate with the server for some reason, then generates an error, CSP’s default behavior is to display the error in an alert box. If you want to handle the error separately, such as log it or display a different message to the user, then write a cspRunServerMethodError JavaScript function. The following example displays the error in an alert box like the default behavior:
function cspRunServerMethodError(errortext,error)
{
//alert('cspRunServerMethodError - cspHyperEventErrorHandler\n\nerrortext:' + errortext + '\n\nerror-object:\n' + JSON.stringify(error, null, 4) );
if (error.code == '401') {
document.location.href = '#(..Link(%request.URL))#'; //reloads the page
}
else {
//...
}
return null;
}
cspHyperEventError object type has the following properties and values:
-
code: corresponds to an HTTP response code or a response code from the XMLHttpRequest object in use. XMLHttpRequest codes may be browser-specific.
-
text: a free text field which corresponds to the current text returned to the cspRunServerMethodError() callback function.
-
serverCode: corresponds to the error number on the server, if available. This value may be null.
-
serverText: the error message from the server, if available. This value defaults to the empty string, that is “”.
-
exception: an exception which triggered the error. This value may be null.
-
arguments: the list of arguments to the function where an exception was trapped. This value may be null and is only populated if exception is defined.