Building Database Applications
One of the most powerful aspects of CSP is that it lets you create dynamic web pages that can directly interact with a built-in, object database. This means that you can quickly build database applications that:
-
Avoid the complexity of mapping relational data to objects
-
Require no complex middleware
-
Can be reconfigured, at runtime, from single-server to multi-tier, multi-server configurations, for true scalability
Note that by using the Caché SQL Gateway, you can build object-based CSP applications that access data in third party relational databases. Caché manages this in an application-transparent way; all the techniques described in this chapter work regardless of whether you choose to store data in the built-in Caché database or a third party database.
CSP is flexible; you can build database applications using a variety of techniques ranging from using higher-level tags that automatically bind data to HTML forms to writing server-side scripts that directly access data using objects. These techniques are outlined below.
Using Objects on a Page
Caché makes it easy to build a database of persistent objects that represent the data for an application. You can use these persistent objects in a web application in a number of ways.
The most straight-forward way to display object data on a page is to use server-side scripts to open the object and write out its contents.
The following examples use the Sample.PersonOpens in a new tab class included in the Caché SAMPLES database. These examples use CSP pages but the techniques described apply to applications built by subclassing the %CSP.PageOpens in a new tab class as well.
Displaying Object Data in a Table
The following CSP page opens an instance of a persistent object, displays some of its properties in an HTML table, and then closes the object:
<html>
<body>
<script language="Cache" runat="SERVER">
// open an instance of Sample.Person
Set id = 1
Set person = ##class(Sample.Person).%OpenId(1)
</script>
<table border="1">
<tr><td>Name:</td><td>#(person.Name)#</td></tr>
<tr><td>SSN:</td><td>#(person.SSN)#</td></tr>
<tr><td>City:</td><td>#(person.Home.City)#</td></tr>
<tr><td>State:</td><td>#(person.Home.State)#</td></tr>
<tr><td>Zip:</td><td>#(person.Home.Zip)#</td></tr>
</table>
<script language="Cache" runat="SERVER">
// close the object
Set person = ""
</script>
</body>
</html>
If you would like to try this, copy the above code into a text file, save it as mytable.csp in your /cachesys/csp/samples directory (cachesys is the installation directory for Caché), and then point your browser at:
http://localhost:57772/csp/samples/mytable.csp
You should see data displayed in a simple HTML table.
Be careful not to do any real work in the /csp/samples directory. If you upgrade Caché, it reinstalls the samples and erases your work.
Displaying Object Data in a Form
Using code similar to that described above, you can display data in an HTML form. This example opens an instance of a persistent object, displays some of its properties in an HTML form, and then closes the object.
<html>
<body>
<script language="Cache" runat="SERVER">
// open an instance of Sample.Person
Set id = 1
Set person = ##class(Sample.Person).%OpenId(1)
If ($Data(%request.Data("SAVE",1))) {
// If "SUBMIT" is defined, then this is a submit
// Write the posted data into the object and save it
Set person.Name = $Get(%request.Data("Name",1))
Set person.SSN = $Get(%request.Data("SSN",1))
Set person.Home.City = $Get(%request.Data("City",1))
Do person.%Save()
}
</script>
<form method="POST">
<br>Name:
<input type="TEXT" name="Name" value="#(..EscapeHTML(person.Name))#">
<br>SSN:
<input type="TEXT" name="SSN" value="#(..EscapeHTML(person.SSN))#">
<br>City:
<input type="TEXT" name="City" value="#(..EscapeHTML(person.Home.City))#">
<br>
<input type="SUBMIT" name="SAVE" value="SAVE">
</form>
<script language="Cache" runat="SERVER">
// close the object
Set person = ""
</script>
</body>
</html>
%request.Data("txt",1) is a string value if the data is less than the local variable limit in Cache. If the data is larger than this, CSP creates a stream with the value of the data in it. If long strings are disabled, the Caché variable limit is 32k. If long strings are enabled then the boundary is much bigger.
If you are creating a form that contains a field that can hold more than 32K of data, code it as follows:
Set value=%request.Data("fieldname",1)
If $isobject(value) {
; Treat this as a stream
} Else {
; Treat this as a regular string
}
Processing a Form Submit Request
In addition to displaying the contents of an object in a form, the preceding example also saves changes to the object when the user submits the form by clicking Save. This works as follows.
When a form is submitted, the values of the controls (including the button initiating the submit) are sent back to the server. In this case, the form is submitted to the same CSP page that initially served the page. You can submit to a different page by setting the value of the forms ACTION attribute.
The CSP server places the submitted values in the %request object Data property. The server-side script at the start of the page tests if the page is being served in response to a submit request by testing if the request parameter Save (which is the name of the submit button) is defined. This should only be defined as a result of a submit request. If this is a submit request, then the script copies the values submitted from the form into the appropriate properties of the object and calls the object:
If ($Data(%request.Data("SAVE",1))) {
// If "SUBMIT" is defined, then this is a submit
// Write the posted data into the object and save it
Set person.Name = $Get(%request.Data("Name",1))
Set person.SSN = $Get(%request.Data("SSN",1))
Set person.Home.City = $Get(%request.Data("City",1))
Do person.%Save()
}
<csp:object> tag
Some of the behavior in the previous examples is provided automatically by the <csp:object> tag. The <csp:object> tag generates the server-side code required to create or open an object instance for use on a CSP page as well as the code for closing it.
For instance, to associate a person with a page:
<csp:object NAME="person" CLASSNAME="Sample.Person" OBJID="1">
<!-- Now use the object -->
Name: #(person.Name)# <br>
Home Address: #(person.Home.Street)#, #(person.Home.City)# <br>
In this case, the <csp:object> tag opens the object of class CLASSNAME with an Object ID of 1 and assigns it to the variable person. In an actual application, the object ID is provided from the %request object:
<csp:object NAME="person" CLASSNAME="Sample.Person"
OBJID='#($Get(%request.Data("PersonID",1)))#'>
Name: #(person.Name)# <br>
Home Address: #(person.Home.Street)#, #(person.Home.City)# <br>
The expression,
$Get(%request.Data("PersonID",1))
refers to the URL parameter PersonID.
The <csp:object> tag with a null OBJID attribute creates a new object of the specified class:
<csp:object NAME="person" CLASSNAME="Sample.Person" ObjID="">
Using the <csp:object> tag is equivalent to including server-side script that explicitly creates an object instance. Refer to the CSP sample page object.cspOpens in a new tab for an example using the <csp:object> tag.
Binding Data to Forms
CSP provides a mechanism for binding the data for an object to an HTML form. This binding uses the standard HTML form and input control tags to define the form allowing you to easily use any HTML editor or design tool with bound forms. The <csp:object> tag specifies an object instance and the attribute cspbind is added to the form and input control tags to indicate how they should be bound.
The CSP compiler recognizes forms containing the cspbind attribute and automatically generates code that:
-
Displays the values of the specified object properties in the appropriate input control.
-
Generates client-side JavaScript functions to perform simple validation (such as required field checking).
-
Generates client-side JavaScript functions to invoke generated server-side methods for saving the bound object.
-
Generates server-side methods that validate and save data input into the form. These methods can be called directly from the page using the CSP Event Broker, or you can invoke them as a result of a form submit operation.
-
Generates a hidden field in the form, OBJID, that contains the object ID value for the bound form.
Here is a simple example of a form bound to an instance of the Sample.PersonOpens in a new tab class:
<html>
<head>
</head>
<body>
<csp:object NAME="person" CLASSNAME="Sample.Person" OBJID="1">
<form NAME="MyForm" cspbind="person">
<br>Name:
<input type="TEXT" name="Name" cspbind="Name" csprequired>
<br>SSN:
<input type="TEXT" name="SSN" cspbind="SSN">
<br>City:
<input type="TEXT" name="City" cspbind="Home.City">
<br>
<input type="BUTTON" name="SAVE" value="SAVE" OnClick="MyForm_save();">
</form>
</body>
</html>
This example uses the <csp:object> tag to open an instance of the Sample.PersonOpens in a new tab class (in this case, with object ID of 1). This object instance is named person. The example then binds the object instance to an HTML form by adding to its form tag an attribute called cspbind with the value person.
The form contains three text input controls, Name, SSN, and City, which are bound to the object properties Name, SSN, and Home.City, respectively, by adding to each of their input tags an attribute called cspbind whose value is the name of the object property the control is to be bound to.
Note that the names of controls used in a bound form must be valid JavaScript identifiers.
The Name control also has an attribute called CSPREQUIRED. This indicates that this is a required field (it must be given a value). The CSP compiler generates client-side JavaScript to test that the user provides a value for this field.
The last control on the form is a button that is defined to invoke the client-side JavaScript function MyForm_save when it is clicked. The MyForm_save function is automatically generated by the CSP compiler. This function gathers the values of the controls in the form and sends them to a server-side method (also automatically generated by the CSP compiler) that reopens the object instance, applies the changes made to the properties, saves the object to the database, and sends JavaScript to the client to update the values in the form to reflect what was saved.
Note that we have defined a HEAD section in this document. This is necessary when using a bound form as this is used a location for any client-side JavaScript that may be generated by the CSP compiler when it processes a bound form.
By convention, the object ID of an object used in a bound form is specified by the URL parameter OBJID. This makes it possible for a bound form to interact with prebuilt pages, such as those used by the CSP Search facility. To use the value of a URL parameter as an object ID, use an expression referring to it in the csp:object tag:
<csp:object NAME="person"
CLASSNAME="Sample.Person" OBJID=#($G(%request.Data("OBJID",1)))#>
Binding to a Property
To bind a particular HTML input control to an object property, do the following:
-
Define a server-side variable that refers to an object instance using the csp:object tag.
-
Create an HTML form using the form tag. Bind the form to the object instance by adding a cspbind attribute to the form tag. Make the value of the cspbind attribute the name of the csp:object tag.
-
Create an HTML input control in the form and add a cspbind attribute to it. Make the value of this cspbind attribute the name of the object property to bind to.
The cspbind attribute lets you bind to many different types of object properties. This is detailed in the following table:
Property | Example | Effect |
---|---|---|
Literal | cspbind=“Name” | Bind the control to a literal property. Display the DISPLAY value of the property. |
Property of Embedded Object | cspbind=“Home.City” | Bind the control to a embedded object property. Display the DISPLAY value of the embedded object property. |
Referenced Object | cspbind=“Company” | Bind the control to the object ID value for a reference property. Display the object ID value for the reference property. |
Property of Referenced Object | cspbind=“Company.Name” | Bind the control to a property of a referenced object. Display the DISPLAY value of the referenced object property. |
Instance Method | cspbind=“%Id()” | Bind the control to return value of an instance method. Display the return value of the method as a read-only field. |
The binding mechanism can be used with most of the available HTML input controls. This is detailed in the following table:
Control | Effect |
---|---|
input type=“TEXT” | Display the value of a property in a text control. |
input type=“PASSWORD” | Display the value of a property in a password control. |
input type=“CHECKBOX” | Display the value (as a boolean) of a property in a check box control. |
input type=“RADIO” | Display the value of a property by selecting the radio button whose value corresponds with the property value. |
input type=“HIDDEN” | Display the value of a property in a hidden control. |
SELECT | Display the value of a property by selecting the choice in the SELECT list whose value corresponds with the property value. You can populate the choices in the SELECT list using a class query by also specifying CLASSNAME, QUERY, and optional FIELD attributes. Refer to the CSP sample page form.csp for an example. |
IMAGE | Display a binary stream property in an IMAGE tag. |
TEXTAREA | Display a property value as text in a TEXTAREA control. |
CSP Search Page with <csp:search> Tag
The csp:search tag creates a generic search page that you can use in conjunction with bound forms to perform lookup operations.
An application user can access the CSP Search page from a page containing a bound form and use it to find objects in the database that match a set of criteria. The user can then select one of these objects and edit it.
The csp:search tag generates a client-side JavaScript function that displays a search page. The search page is displayed by the %CSP.PageLookupOpens in a new tab class.
The csp:search tag includes attributes that give you control over the operation of the search page. These include:
Attribute | Description |
---|---|
CAPTION | Optional. A caption string displayed in the standard search page. |
CLASSNAME | Required. The name of the class upon which the search is performed. |
FEATURES | Optional. A string contains the features argument passed to the JavaScript window.open method when a popup search window is used. This gives you greater control over how popup windows are displayed. |
MAXROWS | Optional. Specifies the maximum number of rows to display in the search results table. The default is 100. |
NAME | Required. The name of the generated client-side JavaScript function that invokes the search page. |
OBJID | The Object ID value of the object displayed when the search page was invoked. This is used to redisplay the old page when the user cancels a search. |
ONSELECT | Optional. In a popup search page, the name of a JavaScript function to call when the user selects a specific search result. This function is called with the Object ID value of the selected object. |
OPTIONS | Optional. A comma-delimited list of search page options. These options include "popup" to create a popup search window and "predicates" to display a drop down list of search predicates. |
ORDER | Optional. A name of a field to order the search results on. |
SELECT | Optional. A comma-delimited list of fields to display in the search result table. If not specified, the WHERE list is used as the SELECT list. |
STARTVALUES | Optional. A comma-delimited list of the names of controls in the form invoking the search page whose contents are used as seed values in the search page. The order of names in the list corresponds to the criteria fields (specified by the WHERE attribute) in the search page. |
TARGET | Optional. In a non-popup search page, specifies the name of the page to which the links in the search result window point. That is the page to display when the user makes a selection. The default is the page invoking the search. |
WHERE | Required. A comma-delimited list of fields used as criteria for the search page. These fields are also shown in the search result table unless the SELECT attribute is specified. |
For example, the following defines a JavaScript function, MySearch; this function displays a popup search window that searches for Sample.PersonOpens in a new tab objects by name:
<csp:search NAME="MySearch" WHERE="Name" CLASSNAME="Sample.Person"
OPTIONS="popup" STARTVALUES="Name" ONSELECT="MySearchSelect">
The ONSELECT callback function for this search page looks like this.
<script language="JavaScript">
function MySearchSelect(id)
{
#server(..MyFormLoad(id))#;
return true;
}
</script>
This function uses the CSP #server()# directive to invoke the server-side method MyFormLoad. The MyFormLoad method is automatically generated as a result of binding the HTML form MyForm to an object using cspbind. This method populates the contents of the form with the property values of an object with object ID id.
For additional examples, refer to the CSP sample pages form.cspOpens in a new tab and popform.cspOpens in a new tab.
Enabling Logging in ISCLOG
To troubleshoot CSP issues, enable logging for Caché by entering the following command in the Terminal:
Set ^%ISCLOG = 2
You can view logging information in the ^ISCLOG global. This global logs events in Caché for use in debugging. For reference, the log levels are as follows:
-
0 — Caché performs no logging.
-
1 — Caché logs only exceptional events (such as error messages).
-
2 — Caché logs detailed information, such as method ABC invoked with parameters X,Y,Z and returned 1234.
-
3 — Caché logs raw information such as data received from an HTTP request.
You can turn Caché logging off with either
Set ^%ISCLOG = 0
or
Kill ^%ISCLOG
In ISCLOG, some entries match Event Log header fields as follows:
ISCLOG | Event Log |
---|---|
Job | Cache-PID |
SessionId | Session-ID |
Tag | Request-ID |
Fields and definitions in ISCLOG are shown in the table below.
Field | Definition |
---|---|
%category |
CSPServer: Logged from cspServer, cspServer2, %request, %response. |
CSPSessionLogged from %session and parts of cspServer and cspServer2 which handle a session. This allows watching the lifecycle of a session. |
|
CSPLicenseLogged from parts of cspServer and cspServer2 which handle a licensing. |
|
Gateway RequestLogged from the GatewayMgr, GatewayRegistry, the Gateway request handler and parts of cspServer2 which handle gateway requests. |
|
%level |
1= Exceptions and errors. |
2=CSPSession information. CSPLicense information. Information from cspServer: the part of the request handling after the %response, %session, and %request have been setup. This includes authentication, license handling, redirection, and calling the CSPpage. |
|
3=Information from cspServer2: the part of handling the request which sets up the %response, %session, %request, and hand-shaking/data transfer with the CSP Gateway. |
|
%job | The value of $job when the ISCLOG request was made. Matches the Cache-PID field from the Event Log header. |
%sessionid |
Entered when available. The value of sessionid at the time the ISCLOG request was made. Matches the Session-ID field from the Event Log header. |
%tag | For the CSP Server, the tag contains the Request id from the gateway (when available). This matches the Request-ID field from the Event Log header. Other loggers may set this value to any value.
Available for use by creators of ISCLOG entries. Stores ID of the request sent to it by the CSP Gateway. It can be used as a filter for generation of ISCLOG entries. Set ^%ISCLOG("Tag","mytagvalue1")=1 Set ^%ISCLOG("Tag","mytagvalue2")=1 Only ISCLOG requests with no tag or with tags of "mytagvalue1" or "mytagvalue2" will be recorded. |
%routine | The name of the routine currently being executed. |
%message | See the sectionMessage Format below. |
Message Format
Messages start with the name of the tag label or method currently being executed. This name is enclosed in square brackets. [MyMethod] rest of messages.
Messages in the CSPSession category also have CSPSession-Id=sessid after the method name. This is needed as session events can be logged before the session is created or after it was destroyed, meaning the SessionId field is empty in the ISCLOG entry.
[MyMethod] CSPSession-Id: 12ty34ui22
Messages in the GatewayRegistry category also have CSPID=cspid(when available) after the method name. This allows the tracking of an individual gateway request from the API call through the Gateway Request Handler.
[MyMethod]CSPID:334r43345 rest of message