You are on page 1of 434

Saba Cloud and Saba Enterprise Release 7.

2
Application Developer Guide

| Limitations on Warranties and Liability | 2

Notice
Limitations on Warranties and Liability
Saba Software, Inc. reserves the right to make changes in information contained
in this document without notice.
In no event shall Saba or its suppliers be liable for any damages whatsoever
(including, without limitation, damages for loss of business profits, business
interruption, loss of business information, or any other pecuniary loss) arising
out of or relating to this documentation or the information contained in it, even
if Saba has been advised of the possibility of such damages and whether arising
from tort (including negligence), breach of contract or otherwise.
This document may only be reproduced and distributed in whole for use by
licensed users. No part of this document may be reproduced in any form for any
other purpose without the prior written consent of Saba Software, Inc.
The software described in this documentation is copyrighted and is confidential
information and a proprietary product of Saba Software, Inc.
U.S. GOVERNMENT RESTRICTED RIGHTS. If licensee is the United States
Government or any contractor thereof, all licenses granted in the License
Agreement accompanying this product are subject to the following: (i) for
acquisition by or on behalf of civilian agencies, as necessary to obtain protection
as "commercial computer software" and related documentation in accordance
with the terms of this Commercial Software Agreement as specified in 48 C.F.R.
12.212 of the Federal Acquisition Regulations and its successors; (ii) for
acquisition by or on behalf of units of the Department of Defense ("DOD") as
necessary to obtain protection as "commercial computer software" and related
documentation in accordance with the terms of this commercial computer
software license as specified in 48 C.F.R. 227-7202-2 of the DOD F.A.R.
Supplement and its successors.
Saba, the Saba logo and Centra are registered trademarks of Saba Software, Inc.
and Saba and Saba Centra product names are the trademarks of Saba Software,
Inc. All other product names mentioned in this manual are the property and may
be trademarks or registered trademarks of their respective owners, and are used
for identification purposes only.

2013 Saba Software, Inc. All rights reserved.

| Limitations on Warranties and Liability | 3

| Introduction | 4

Published: 11/22/2013
Part Number: G-ADG-SCR72000-1

| TOC | 5

Contents
Preface: About This Guide
..........................................................................................xi
Intended Audience
.....................................................................................................................................................xi
How to Contact Saba
..................................................................................................................................................xi

Chapter 1: Application Platform Overview


..............................................................13
Introduction
...............................................................................................................................................................14
System Architecture
..................................................................................................................................................14
Security Issues
...........................................................................................................................................................15
Internationalization Support
......................................................................................................................................15
Multiple Language Support
..........................................................................................................................15
Format Localization Support
.........................................................................................................................15
Localizable Objects
.......................................................................................................................................16

Chapter 2: Customizing Saba


....................................................................................17
Introduction to Saba Customizations
........................................................................................................................18
UI Configuration: Overview
.....................................................................................................................................18
Script Customizations: Overview
..............................................................................................................................18
Coding Customizations: Overview
...........................................................................................................................19

Chapter 3: WDK Page Tutorial


.................................................................................21
Overview
...................................................................................................................................................................22
Application Page Life Cycle
.........................................................................................................................22
Working with WDK Pages
............................................................................................................................23
Tutorial Description
......................................................................................................................................23
Writing a Simple Portlet
...........................................................................................................................................23
First Portlet: "Hello World"
...........................................................................................................................24
Following Best Practices: An Improved "Hello World"
................................................................................32
Using Widgets
...........................................................................................................................................................35
Model File with Widget
................................................................................................................................35
View File with Widget
...................................................................................................................................37
Command Objects and Visitors
.................................................................................................................................38
The Control File
............................................................................................................................................39
The Command Object
...................................................................................................................................39
Model File with <wdktags:execute>
.............................................................................................................44
View File
.......................................................................................................................................................48

| TOC | 6

Chapter 4: WDK Widgets


...........................................................................................49
Widget Overview
......................................................................................................................................................51
Using Widgets
...........................................................................................................................................................51
Defining in the Model File
............................................................................................................................52
Including in the View File
.............................................................................................................................57
Widget Format
...........................................................................................................................................................58
'name' attribute
..............................................................................................................................................58
<id>
...............................................................................................................................................................58
<data>
............................................................................................................................................................58
<wdktags:attachTo>
......................................................................................................................................59
<initializeFromRequest>
...............................................................................................................................60
<viewOnly>
...................................................................................................................................................61
Widgets and Model Objects
......................................................................................................................................61
The Standard Widget Library
....................................................................................................................................62
<wdk:attachments>
.......................................................................................................................................66
<wdk:breadCrumb>
......................................................................................................................................68
<wdk:chart>
..................................................................................................................................................70
<wdk:chartToolbar>
......................................................................................................................................77
<wdk:componentPicker>
..............................................................................................................................81
<wdk:componentWidget>
.........................................................................................................................................84
<wdk:customFields>
.................................................................................................................................................89
<wdk:dateInput>
.......................................................................................................................................................91
<wdk:deleteLink>
.....................................................................................................................................................94
<wdk:domainPicker>
................................................................................................................................................95
<wdk:file>
.................................................................................................................................................................98
<wdk:finderQuery>
.................................................................................................................................................100
<wdk:finderResult>
................................................................................................................................................115
<wdk:finderControl>
..............................................................................................................................................144
<wdk:form>
............................................................................................................................................................146
<wdk:frameworkLink>
...........................................................................................................................................147
<wdk:functionality>
................................................................................................................................................149
<wdk:genericText>
.................................................................................................................................................150
<wdk:hiddenField>
.................................................................................................................................................153
<wdk:impresence>
..................................................................................................................................................154
<wdk:input>
............................................................................................................................................................155
<wdk:impresence>
..................................................................................................................................................161
<wdk:link>
..............................................................................................................................................................162
<wdk:list>
...............................................................................................................................................................178
<wdk:lovPicker>
.....................................................................................................................................................184
<wdk:multistep>
.....................................................................................................................................................187
<wdk:multisection>
................................................................................................................................................188
<wdk:pageText>
......................................................................................................................................................190
<wdk:pageTitle>
.....................................................................................................................................................191

| TOC | 7

<wdk:pageWidget>
.................................................................................................................................................195
<wdk:parameters>
...................................................................................................................................................205
<wdk:presence>
......................................................................................................................................................206
<wdk:promptForSave>
............................................................................................................................................208
<wdk:sabaPicker>
...................................................................................................................................................210
<wdk:saveLink>
......................................................................................................................................................212
<wdk:script>
...........................................................................................................................................................213
<wdk:table>
............................................................................................................................................................217
<wdk:textarea>
........................................................................................................................................................228
<wdk:timeInput>
.....................................................................................................................................................230
<wdk:tree>
..............................................................................................................................................................232
<wdk:validatorWidget>
..........................................................................................................................................257
Component Dictionary Support
..............................................................................................................................258
Example
..................................................................................................................................................................259

Chapter 5: Model Objects


........................................................................................265
Overview
.................................................................................................................................................................266
Model Object Lifecycle
...........................................................................................................................................266
Defining and Using Model Objects
........................................................................................................................267
Declaring a Model Object
...........................................................................................................................268
Initializing and Saving/Restoring State
...................................................................................................................269
Decoding State
............................................................................................................................................270
Encoding State
............................................................................................................................................270
Managing Widgets
..................................................................................................................................................270
Special WDK Tags
..................................................................................................................................................271
getModelObject()
....................................................................................................................................................272
<wdktags:execute>
..................................................................................................................................................272
Advanced Model Object Techniques
......................................................................................................................272
Calling the Model Object From a Second Page
..........................................................................................273
Passing Protected State to Another Model Object
......................................................................................273

Chapter 6: Coding Customization (Java)


................................................................275
Types of Objects
......................................................................................................................................................276
Infrastructure Classes
..................................................................................................................................276
Class Structure
............................................................................................................................................277
Typical Tasks
...........................................................................................................................................................280
Finding a Specific Reference Object
..........................................................................................................280
Finding all objects meeting certain criteria
.................................................................................................281
Modifying an Object
...................................................................................................................................282
Creating a new object
..................................................................................................................................283
Executing Java Code
...............................................................................................................................................283
Integrating with WDK pages
.......................................................................................................................283
Notification Actions
....................................................................................................................................285

| TOC | 8

Non-WDK Programming
............................................................................................................................285

Chapter 7: Tools and Utilities


...................................................................................289
WDK Page Compiler
..............................................................................................................................................290
Java ANT Makefile
.................................................................................................................................................291

Chapter 8: Designing Portlets


..................................................................................293
Overview
.................................................................................................................................................................294
Writing a Portlet WDK Page
...................................................................................................................................294
Portlet Control Files
....................................................................................................................................295
Portlet View Files
........................................................................................................................................295
Portlet Model Files
......................................................................................................................................296
Links from Portlet Pages
.............................................................................................................................296
Testing a Portlet Page
..................................................................................................................................296
Coding a Portlet Manager
.......................................................................................................................................296
Initializing the Portlet Manager
..................................................................................................................296
Registering a Portlet's Page
.........................................................................................................................297
Registering Required Parameters
................................................................................................................298
Registering Help Pages
...............................................................................................................................298
Installing a Portlet
...................................................................................................................................................299

Chapter 9: Writing Web Services


............................................................................301
Overview
.................................................................................................................................................................302
Command Object Requirements
.............................................................................................................................302
Generating a WSDL File
.........................................................................................................................................304
Installing a Custom Web Service
............................................................................................................................308

Chapter 10: Localizable Objects


..............................................................................309
Chapter 11: WDK Page Tutorial
.............................................................................313
Overview
.................................................................................................................................................................314
Application Page Life Cycle
.......................................................................................................................314
Working with WDK Pages
..........................................................................................................................315
Tutorial Description
....................................................................................................................................315
Writing a Simple Portlet
.........................................................................................................................................315
First Portlet: "Hello World"
.........................................................................................................................316
Following Best Practices: An Improved "Hello World"
..............................................................................324
Using Widgets
.........................................................................................................................................................327
Model File with Widget
..............................................................................................................................327
View File with Widget
.................................................................................................................................329
Command Objects and Visitors
...............................................................................................................................330

| TOC | 9

The Control File


..........................................................................................................................................331
The Command Object
.................................................................................................................................331
Model File with <wdktags:execute>
...........................................................................................................336
View File
.....................................................................................................................................................340

Chapter 12: Tips and Tricks


.....................................................................................341
Overview
.................................................................................................................................................................342
Making SQL Calls from a Client Command
..........................................................................................................342
Using Dynamic Delegates
.......................................................................................................................................344
Extending Saba's EJBs
............................................................................................................................................345
Extending the Saba EJB
..........................................................................................................................................345
Modifying ejb-jar.xml
.............................................................................................................................................349

Chapter 13: Sample Java Program


..........................................................................353
Sample Code Overview
...........................................................................................................................................354
Files
.............................................................................................................................................................354
Configuring the Sample Code
.....................................................................................................................354
Compiling and Running
..............................................................................................................................354
Sample Code Files
..................................................................................................................................................355
build.bat
.......................................................................................................................................................356
run.bat
.....................................................................................................................................................................356
APIExamples.java
...................................................................................................................................................358

Chapter 14: Security Configuration


........................................................................413
Overview
.................................................................................................................................................................414
Notes on Saba Configuration
..................................................................................................................................414
Special Users
...............................................................................................................................................414
Saba J2EE Roles
.........................................................................................................................................414
Security.xml
................................................................................................................................................415
Login Modules
............................................................................................................................................415
Public and Private Keys
...............................................................................................................................416
Using Alternate Login Modules
..............................................................................................................................416
WebServerSSOLoginModule
......................................................................................................................416
LDAPLoginModule
....................................................................................................................................416
NTLoginModule
.........................................................................................................................................418
Using a Third Party Login Module
.............................................................................................................418
Writing a Custom Login Module
................................................................................................................418
Web Server Single-Sign ....................................................................................................................................418
On
How It Works
..............................................................................................................................................419
Setup and Configuration
.............................................................................................................................419
J2EE Single Sign-On
..............................................................................................................................................421
Disable Composite Principal
.......................................................................................................................421
Add Common Roles to Web Descriptor
......................................................................................................421

| TOC | 10

Security Constraints
....................................................................................................................................422

Chapter 15: Eclipse Plug-in


......................................................................................423
Overview and Requirements
...................................................................................................................................424
Installing and Configuring the Plug-in
....................................................................................................................424
Installing
......................................................................................................................................................424
Setting Up the Project
.................................................................................................................................425
Configuration
..............................................................................................................................................426
Editing WDK Pages
................................................................................................................................................428
Model Pane
..................................................................................................................................................429
Preview Pane
...............................................................................................................................................430
Labels View
.................................................................................................................................................431

Preface
About This Guide
The purpose of this document is to provide the information and guidelines that application developers need to customize
Saba Cloud using Saba's public Java API. The document demonstrates how to customize existing application pages as
well as how to build new pages, taking advantage of the application functionality encapsulated in Saba's Java API.

Intended Audience
This guide is intended for use by the application developers responsible for developing Web applications using Saba's
Web development platform. This document is intended for Saba consultants and for Saba customers. It is also used
internally as a ramping tool for new developers.
The information in this guide assumes you have the following minimum skill set and/or knowledge:

HTML and JavaScript languages


Basic familiarity with XML
Java development
Standard Web-based GUI, such as Netscape Navigator or Microsoft Internet Explorer

How to Contact Saba


For contact information, see our company Web site: http://www.saba.com.

Chapter

1
Application Platform Overview
Topics:

Introduction
System Architecture
Security Issues
Internationalization Support

This chapter provides an overview of Saba's application development platform.


The chapter describes Saba's system-level architecture and provides detailed
information about Saba's EJB-based application programming model. It also
describes security and internationalization issues.

| Application Platform Overview | 14

Introduction
Saba's application development platform is based on 3-tier distributed object architecture. Saba's 3-tier architecture
comprises the following elements:

Presentation layer - defines the client-side user interface


Application server layer - handles processing of business logic
Persistence layer - manages persistent objects in a back-end database

The following diagram illustrates Saba's 3-tier architecture:

Saba's 3-Tier Architecture


The application server includes interfaces for interacting with both the database and the web application page through
the Saba's Java API. The API makes it possible to access and manage business objects while ensuring that security
permissions and business validations are not violated.
Saba application pages can display application elements to an end-user. They are compiled by the application server
and are designed to be viewed inside a standard Web browser. They enable you to generate content dynamically through
embedded Java statements that access Saba's Java API.
Because presentation logic is separate from business logic, the code designed for displaying information to the end-user
can be separate from code designed for generating data. This separation makes it possible to change one layer without
changing the other.

System Architecture
Saba's system-level architecture consists of several functional modules layered on top of a set of shared business objects
and core services that provide common application functionality. The functional modules include:

Learning
Ordering
Performance
Content Management
Resource Managment

Each functional module includes objects and services specific to the particular application, as well as a set of common
business objects that provide core functionality.
The common business objects layer defines a set of business objects shared across all Saba applications and supports
linkages between the applications.
Supporting the applications and common business objects layers is the core services layer, which provides common
application functionality, including services such as payment processing, business rule configuration, and reporting.
The foundation of Saba's system architecture is the platform layer, which provides the underlying infrastructure for
developing and managing complex enterprise applications. This includes:

Definition of a core set of abstractions for security, persistence components, and so on, through Saba's Business
Object Server.

| Application Platform Overview | 15

Ability to generate web-based application pages with dynamic content, through Saba's Web Developer Kit.

The layered architecture avoids circular dependencies by requiring that all dependencies be directed downwards. A
vertical application can have dependencies on one or more sets of common business objects but not on other applications.
Similarly, common business objects can depend on core services and on other common business objects, but not on
applications.
This following diagram illustrates Saba's system architecture:

Saba System Architecture

Security Issues
Access to most functionality in a Saba application requires user authentication. A successful login results in a set of
privileges for the individual user in the session environment, along with a session key handle. The privileges take into
account the security roles assigned to the user, as well as any other restrictions associated with the particular environment
or access. Because security in Saba applications is implemented at the business object level, the system checks security
privileges whenever saving or restoring anything to or from the database.

Internationalization Support
Saba enables you to create fully internationalized applications that you can easily localize. Saba's provides support at
the back?end database level for storing locale-specific values.

Multiple Language Support


Saba provides localized versions of its applications for many spoken languages. For each language, the application
interface is fully translated, including all screen text, menus, buttons, messages, and prompts. Online help and selected
product documentation are also translated. A single Saba installation can include multiple languages in a single database.

Format Localization Support


In addition to multiple language support, Saba also provides localization formats to fully localize the user experience.

| Application Platform Overview | 16

Localizable Objects
Saba provides the ability to define locale-specific values for some attributes (usually the name and description attributes)
of many objects in the system. When a user defines a value for a localizable object, the system stores the value in a table
specific to the user's locale. Other users who share the locale see this value. However, users with a different locale do
not see this value. Instead, they see only the value entered by users who share their locale.
The only time a user (user "A") belonging to a locale (locale "A") can see the value associated with locale "B" is when
user "A" retrieves a record that has been created and saved by a user belonging to locale "B".
For a complete list of localizable objects, see Appendix C, "Localizable Objects"Appendix C, "Localizable Objects" on
page C-1.

Chapter

2
Customizing Saba
Topics:

Introduction to Saba
Customizations
UI Configuration: Overview
Script Customizations: Overview
Coding Customizations: Overview

Saba provides several ways to customize Saba Cloud, ranging from simple,
application-wide theme changes to intricate customizations written in Java.

| Customizing Saba | 18

Introduction to Saba Customizations


Most customers want to customize Saba Cloud. Some customers make minor changes, such as replacing the Saba logo
with another graphic or changing the color scheme. Other customers make elaborate changes to the way application
pages are displayed or add new functionality to the application pages.
The Saba API makes it possible to customize Saba Cloud in the following ways:

Configuration: Settings that affect how the Saba applications run. Most changes here affect all Saba sites running
from a single installation. This includes such operations as changing attribute constraints, custom fields, and business
rules. (These can all be changed with the Saba Administrator UI.) This material is covered in the Catalog Administrator
Guide. It is not further discussed in this book.
Configurable UI: This covers look-and-feel options that apply across an entire Saba desktop (a view into a Saba
site). Changes to the UI do not let you modify behavior of specific application pages.
Scripting: This covers modifications to Saba application pages that do not require coding in Java. This can include
modifying pages to present the same data in different ways, as well as other web-related changes (such as adding
links to other application pages).
Coding: This covers any customizations that require Java code. This is the most powerful and versatile kind of
customization. Custom Java code can be integrated with Saba application pages, or launched separately.

These options are listed in order of increasing complexity and power. If you have a customization to make, look down
that list and use the first option that works. For example, to change the color scheme for an entire Saba installation, make
a theme customization, perhaps by modifying one of the style sheets.

UI Configuration: Overview
Saba's System Administration screens provide a wide range of UI configuration options. You can easily customize the
system's color schemes, menu configurations, and graphics. You can also set up several different user interface themes
for the same Saba site and make different themes available for different users. For example, you can use one color
scheme for system administrators and another scheme for ordinary users.
For detailed information about the Saba system administration screens, refer to the System Administrator Guide.

Script Customizations: Overview


The Saba engine reads pages written in special script languages, processes them, and generates web pages for users to
view and use. You can modify the application pages' functionality by editing the scripts or creating new script pages.
Application pages are written in the Saba WDK Language, an XML-based language that uses the control/model/view
paradigm to separate data from presentation. This language is described in Chapter 3, "Scripting Customization
(WDK)"Chapter 3, "Scripting Customization (WDK)".
Note: Previous Saba releases supported a different kind of application page, called "Saba Pages". In this release,
Saba pages have been entirely superseded by WDK pages.
Saba application pages are written in the WDK Page format. This is an XML-based format for generating application
pages. The page production is separated into separate layers-a model XML file generating the data, a view file formatting
the data, and a control page matching up the model file with the view file. This enables developers write application
pages in a modular way. For example, if the same data might be represented in five different formats, developers can
write a single model file and five different view files.

| Customizing Saba | 19

Coding Customizations: Overview


Saba provides a Java API that client applications can invoke to retrieve, modify, and delete information from the Saba
data store. This is useful for situations in which other customization options are insufficient. For example, you can
change the way a particular application page displays its data by modifying scripts in the existing Saba or WDK pages.
To retrieve data according to your own criteria, you can write custom Java code.
The usual way to invoke Java code is through a Saba application page. There are several ways to do this, for either WDK
pages or Saba script pages. For example, WDK pages provide a tag that invokes a Java command object. When the Saba
engine processes the page, it instantiates the appropriate command object, which in turn generates an XML node. This
XML node is inserted into the model page, making its contents available to any view pages.
In addition, you can write stand-alone Java applications that perform operations on the Saba data store. These applications
could be launched on their own, instead of being integrated with Saba application pages. This is useful for testing -you
can launch portions of your Java code and test them on their own, and integrate the code with the Saba pages after it is
debugged. In addition, there may be times when you want to write a stand-alone application that interacts with the Saba
data store. For example, to perform a large batch operation (such as importing a large amount of data, or making changes
to several records at the same time), write a one-use Java program to perform the operation.
For more information about the Java API, see Chapter 6, "Coding Customization (Java)"Chapter 6, "Coding Customization
(Java)".

Chapter

3
WDK Page Tutorial
Topics:

Overview
Writing a Simple Portlet
Using Widgets
Command Objects and Visitors

This chapter describes how to write a WDK application page. The chapter covers
a wide range of possible page types, from the very simple to the elaborate.

| WDK Page Tutorial | 22

Overview
Saba application pages are written in our WDK format. This format is described in detail in Chapter 3, "Scripting
Customization (WDK)"Chapter 3, "Scripting Customization (WDK)". A WDK application page is made of three files:

The control file is the master file. It specifies the other two files used by the application page, assigns a unique
identifier number to the page, and specifies certain other high-level resources (such as the widget library used by
the page).
The model file specifies the data displayed in the application page, and also specifies which widgets are available to
the page.
The view file specifies how the data and widgets are arranged on the application page.

These files are all written in XML. The control file uniquely identifies the application page. That is, by specifying a
control file, you have specified an application page. By contrast, a single model file can be used by several different
application pages. For example, a single model file can present information about users, and several different view files
can present the model file's data in different ways. For example, one view file can display a table with all the data,
another can display only certain columns of the table, and another can display a stripped-down version suitable for
displaying in a portlet.
It is possible to use a single view file with several different model files, but this is far less common than using several
views with a single model. Usually, a view file is tailored to a specific model file.

Application Page Life Cycle


The life-cycle of a Saba application page is more complex than that of an ordinary web page, and it affects the way Saba
pages are written and maintained.
Each of the three files that make up a Saba application page serves a different function and is used at a different time.

The model file is compiled to create a Java class. This compilation is done once for each model file. Once the Java
class is created, it can be used every time a user opens an application page which uses that file. The compiled Java
class generates XML output, which contains the data displayed on an application page.
The view file is used to transform the XML output into the web page that is displayed by a user's brower. It produces
a web page with XHTML and Javascript. There is not usually any Java in the output, so the user's browser does not
need to be Java-enabled. (Of course, you could write an application page which runs a Java applet if you wished.)
The control file identifies the model file and view file used by the application page. When you want to add an
application page into the system, you do it by creating a link (or a menu item) which opens that control file; Saba
then uses the control file to open the view and model file.

Thus, when a user launches an application page, the system follows this sequence of events:
1. The system checks the control file to see what model file and view file are used by the application page.
2. The system checks to see if the model file has already been compiled into a Java class. If it has not, the system
compiles that model file.
3. The system creates a Java object from that class, and uses the object to generate the XML data used by the application
page.
4. The system uses the view file (and any associated stylesheets, like the widget definition stylesheets) to transform
the XML data into an XHTML/Javascript file.
5. The system sends that file to the user's browser, which displays the finished application page.
If the application page is a portal, the system also follows this sequence for each portlet on the page.
The way Saba constructs the finished application page has a few implications you should keep in mind:

The first time a person runs an application page, Saba has to compile the model file into a Java class. This greatly
slows down the application's response time for that page. The next time anyone views that page (even with different

| WDK Page Tutorial | 23

data), Saba can use the cached Java class, greatly improving response speed. (You can also use a Saba utility to
compile all the application pages at once, instead of waiting for users to load the pages.)
When a user views an application page, Saba checks to make sure that the compiled model class is up-to-date. If the
model file is more recent than the Java class, Saba recompiles the class. However, some application servers keep
caches of Java classes used. As a result, if you change a model file while Saba is running, and the model file has
already been used since the last time the application server was launched, the server may use its cached version of
the class instead of the recompiled version based on the new model file. This keeps the changes from showing up
in the compiled application page. For that reason, after you make changes to model files, you may need to restart
your application server to view the changes. (This is not necessary if you make changes to the view or control file.)

Working with WDK Pages


There are two common situations in which one might edit a WDK page.

You might create a brand-new page entirely from scratch. In that situation, you would need to create a control, model,
and view file, and insert the page into the application.
You might edit an existing WDK page. In that case, you might duplicate an existing page, modify the duplicate, and
insert the new page into the application. You could also edit the existing page in place.

If you are editing an existing application page, you can ignore much or all of what you see in the files and make a small
alterations. For example, you might decide that a particular table should show less information than it does. You could
do this by editing the view file to remove a particular column, and not altering the model or control files at all. On the
other hand, if you are creating a new page, you must understand how all three files are constructed, but you might not
need to know subtler details about how the components work. The page that you construct can be simpler than typical
Saba application pages.
Of course, there isn't always a clear line between creating new pages and editing existing ones. Usually when you create
a brand new application page, do it in stages, gradually building up the functionality from one draft of the page to the
next (and borrowing code from existing application pages that do similar things). That is how this tutorial is structured.
It begins by describing how to create a simple portlet, then describes how to add functionality to the portlet, turn it into
an application page, and then add advanced features.

Tutorial Description
This tutorial describes how to create a wide number of WDK pages. It shows how to build simple application pages
from scratch, and also how to modify simple pages to create more complex ones.
Note: You may never need to create an entire application page. Usually, you are able to design web pages by
modifying existing WDK application pages.
The tutorial covers the following steps:

Writing a simple portlet


Retrieving internationalizable text from Java resources
Using widgets in application pages
Using a command object to construct XML data

Writing a Simple Portlet


This section describes how to write a simple Saba portlet. A portlet is a special kind of application page that is designed
to be displayed on a portal page along with other portlets. It is written the same way as any other application page, but
with special restrictions. These are fully detailed in Appendix A, "Designing Portlets"Appendix A, "Designing Portlets",

| WDK Page Tutorial | 24

but portlets should be small, simple, and not make assumptions about how much screen space they can have. This ensures
that Saba can resize the portlet as necessary, and still have it be readable when displayed.
Because a portlet is small and self-contained, and because it is easy to install into Saba, portlets are a very useful way
to design and test WDK functionality. For example, very often the meat of an application page is a table displaying
information. You can build and test that table in a portlet. Then, once that code is working, you can expand it into a full
application page.
This section covers the following steps:
1. Writing a portlet that displays simple text, installing it in Saba, and viewing it.
2. Expanding the portlet to use some common widgets, and to follow Saba's best practices.

First Portlet: "Hello World"


This section describes how to write a simple portlet which displays static text. Needless to say, there are simpler ways
to do this than writing a Saba application page. If you want to write a "message of the day" portlet, you could write it
as an XHTML page. This section describes how to write it as an application page so you can use it as a framework for
more elaborate pages.
Like any other application page, a portlet is made up of three files: a model file containing the data, a view file which
presents the data for display, and a control file which specifies the model and view files, along with some other high-level
information. In addition, a portlet requires a special-purpose Java object. For each portlet, you must create and install a
class based on the AbstractPortletPageManager class. This object is used to inform the system where the
control file for the portlet is located. The system can then examine the control file to find the model and view files. (With
a regular application page, the control file is linked to some other way-either through a link widget on some other page,
or by being added to the menu system.)
This section lists typical control, application, and view files, as well as a portlet page manager object which loads the
portlet. The control, model, and view files can be installed anywhere inside the Saba application's document root. In
this example, as is usually the case, the three files are all installed in the same directory. The portlet page manager object
must be compiled, and the class file installed anywhere in the application's Java path.

| WDK Page Tutorial | 25

"Hello World" Control File


The control file specifies the view and model files used by the portlet. Control files vary little from one application page
to the next.

<?xml version="1.0" encoding="UTF-8"?>

<?cocoon-process type="portlet"?>

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:wdk="http://www.saba.com/XML/WDK">

<!-- Saba reserves IDs up to 500000 -->

<rdf:Description id="511111">

<rdf:type resource="http://www.saba.com/XML/WDK/Control"/>

<wdk:version>1.0</wdk:version> <wdk:model
rdf:resource="helloWorldPortlet.xml"/> <wdk:view
rdf:resource="helloWorldPortlet.xsl"/> <wdk:widgets
rdf:resource="/wdk/xsl/widget/wdk_widgets.xsl"/>

</rdf:Description>

</rdf:RDF>
The bold-face sections are areas you usually would need to change when you create a new control file. In addition, if
the model file contains links to model files (including itself), include a <wdk:links> node, as described in
"<wdk:links>""<wdk:links>" on page 3-19. Other than that, your various conrol files can all look pretty much the same.
The parts of the control file that might change include:

Cocoon process type: The <?cocoon-process> tag must have a type attribute. This attribute is set to portlet
for portlet pages, and wdk for ordinary application pages.
ID Number: Every application page (including portlet pages) must have a unique ID number. Saba reserves all ID
numbers up to 500,000. You can use any integer above that for your application pages. The ID number is specified
in the <rdf:description> node's id attribute.
Model and view pages: The control file specifies the model file and the view file used by this page or portlet. This
is specified either with the full URL of the model and view files, or (more often) with the path relative to the location
of the control file. Thus, if the model, view, and control files are all in the same directory (as is usually the case),
you can enter the names of the files.

| WDK Page Tutorial | 26

"Hello World" Model File


The model file contains all the data displayed by the application page or portlet. As noted above, Saba compiles the
model file to create a Java class. It then uses that Java class to generate the XML data for the application page.
In this case, the only data displayed by the page is a simple line of text. To begin with, we hard?code that text into the
portlet. However, as you'll see later in the tutorial, that is not the ideal way to display text in an application page. It is
used here as an example.

<?xml version="1.0"?>

<xsp:page language="java"

| WDK Page Tutorial | 27

xmlns:xsp="http://www.apache.org/1999/XSP/Core"
xmlns:wdktags="http://www.saba.com/XML/WDK/taglib">

<xsp:structure>

<xsp:include>com.saba.*</xsp:include>

</xsp:structure>

<wdk:page xmlns:wdk="http://www.saba.com/XML/WDK">

<!-- the head element contains standard header data


for all WDK model files -->

<wdk:head>

<!-- No input or output parameters for this portlet. -->

<!-- the title of the page or portlet -->

<wdk:title>Hello World</wdk:title>

<!-- WDK labels, usable by the view file -->

<wdk:labels>
</wdk:labels>

<wdk:label name="hello_text">Hello, world!</wdk:label>

</wdk:head>

<wdk:form method="POST">

<!-- Model information is in this tag. However, this portlet has no


information here.-->

<wdk:model>

| WDK Page Tutorial | 28

<!-- No model information needed. -->

</wdk:model>

</wdk:form>

<!-- Widgets are defined here. -->

<wdk:widgets>

<!-- No widgets in this portlet. -->

</wdk:widgets>

</wdk:page>

</xsp:page>
As you can see, this model file contains very little information. The only information specific to the portlet is the text
associated with the hello_text label, "Hello world!"

"Hello, World" View File


The view file is an XSLT stylesheet that transforms the data in the model file into a viewable Saba application page or
portlet. In this case, the view file can be very simple. It displays the text of the model file's hello_text variable in
an XHTML page.

<?xml version="1.0" encoding="UTF-8"?>

<?wdklint ignoreRules="XSL_HTML"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"


xmlns:wdk="http://www.saba.com/XML/WDK"

| WDK Page Tutorial | 29

xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rss10="http://purl.org/rss/1.0/">

<xsl:import
href="/wdk/xsl/view/wdk_defaultview.xsl"/>

<xsl:import
href="/wdk/xsl/view/wdk_widgetlayout.xsl"/>

<xsl:template match="wdk:model">

<!-- Portlet contents should be put inside an HTML table


or a table widget. --><table width="100%" height="100%" border="0"
cellspacing="1" cellpadding="0">
<tr><td><xsl:value-of
select= "$wdkLabel[@name='hello_text']"/></td></tr> </table>

</xsl:template>

</xsl:stylesheet>
The defaultview.xsl file is responsible for creating the general framework of the portlet, including an <HTML>
node. To fill in the contents of the <HTML> node, that stylesheet invokes the wdk:model template which you write.
The result is a portlet page containing a single <p> node, which contains the value of the hello_text variable:

<HTML>

<table width="100%" height="100%" border="0"

cellspacing="1" cellpadding="0">

<tr><td>Hello, world!</td></tr>

</table>

</HTML>
Note: As noted in Appendix A, "Designing Portlets"Appendix A, "Designing Portlets", a portlet should consist of
either an HTML table (and its contents), or a Saba table widget. Thus, the "Hello, world!" text is put inside an
HTML table with one row and one column.

| WDK Page Tutorial | 30

"Hello, World" Page Manager Object


In order for the application to load the portlet, it must know where the page files are installed. The application finds this
out by instantiating a page manager object specified for the portlet. That object, in turn, registers the control file with
the system.

| WDK Page Tutorial | 31

When you write the portlet page manager, you must know where the application page's control file is installed. (The
Saba application can then examine the control file to find the locations of the model and view files.) In this case, we
assume that the control file is installed as /tutorial/portlet/helloWorldPortlet.rdf.

package com.saba.client.portal;

import com.saba.exception.SabaException;

import com.saba.portal.AbstractPortletPageManager;

import com.saba.portal.PortletPageManager;

/**

* This class registers the page for the Hello World portlet.

*/

public class HelloWorldPortletPageManager extends AbstractPortletPageManager


implements PortletPageManager

/** The constructor method doesn't need to do anything...*/

public HelloWorldPortletPageManager() {

/**

* Initialize the portlet. You must register the portlet by calling

| WDK Page Tutorial | 32

* registerPage.

*/

public void init() throws SabaException {

/* register the control page for this portlet */

registerPage(PortletPageManager.kDefaultDisplay,
"/tutorial/portlet/helloWorldPortlet.rdf");

} /* init */

} /* HelloWorldPortletPageManager */
After you compile this class, install it into Saba. Place the class file in the Saba application server's Java class path, then
add the portlet and its manager using the Saba system administration module (as described in the System Administrator
Guide).

Following Best Practices: An Improved "Hello World"


The simple portlet described above shows how to display text, or indeed, to display any xHTML code. However, it does
not follow the WDK's best practices.
As a general rule, you should not hard-code text in your application pages. In the case of the above portlet, the text
"Hello, world!" is coded directly into the application page's model file. This makes it difficult to internationalize the
portlet if you are providing it to locales which use a different language. Instead of hardcoding text into your application
page, all the text on an application page should be loaded either from internationalizable resources, or from the Saba
application database.
Note: Saba's WDK Eclipse Plug-in provides a feature for editing resource labels, as long as they are named
according to Saba's convention. This tutorial follows that naming convention. In addition, you can use the Eclipse
Plug-in to create the custom labels, and the resource file for them. For more information, see Appendix H, "Eclipse
Plug-in"Appendix H, "Eclipse Plug-in".
Create a normal Java resource bundle for the page's labels. If you wish, this resource bundle can contain versions of the
resources for various locales, or a default version of the resources. Install the resource bundle into the Saba application
server's load path.

| WDK Page Tutorial | 33

Create appropriate resource entries in that resource bundle. In this case, we assume that the resource bundle is named
"my_custom_pages", and we add a single entry to the default resource file:

kI18n511111helloWorldLabel=Hello, world!
Note the naming convention followed. All of an application page's resources should be given names that fit the pattern

kI18n<page_ID_number><label_name>
The model file can then load any resources it needs from that bundle. To use the value of that label, you would change
the model file described above to the following. (Once again, changes are in bold-face.)

<?xml version="1.0"?>

<xsp:page language="java"
xmlns:xsp="http://www.apache.org/1999/XSP/Core"
xmlns:wdktags="http://www.saba.com/XML/WDK/taglib">

<xsp:structure>

<xsp:include>com.saba.*</xsp:include>

</xsp:structure>

<wdk:page xmlns:wdk="http://www.saba.com/XML/WDK">

<!-- the head element contains standard header data


for all WDK model files -->

<wdk:head>

<!-- No input or output parameters for this portlet. -->

<!-- the title of the page or portlet -->

<wdk:title>Hello World</wdk:title>

<!-- Load the resource bundle... -->

| WDK Page Tutorial | 34

<wdktags:i18n.load resource="my_custom_pages"/>
by the view file -->

<!-- WDK labels, usable

<wdk:labels>

<wdk:label name="hello_text"><wdktags:i18n.label
name="kI18n511111helloWorldLabel"/></wdk:label>

</wdk:labels>

</wdk:head>

<wdk:form method="POST">

<!-- Model information is in this tag. However, this portlet has no


information here.-->

<wdk:model>

<!-- No model information needed. -->

</wdk:model>

</wdk:form>

<!-- Widgets are defined here. -->

<wdk:widgets>

<!-- No widgets in this portlet. -->

</wdk:widgets>

</wdk:page>

| WDK Page Tutorial | 35

</xsp:page>
When Saba compiles the Java code for the model page, it transforms the <wdktags:i18n.label> node into an
appropriate command to load the text from the resource bundle. Thus, the page uses whatever label is best suited for
the current locale.
The view and control files do not need to be changed to make use of the new label. The view file loads the contents of
the hello_text XML label, as before.

Using Widgets
The previous example showed how to create an application page or portlet which uses straight xHTML. You could use
exactly the same technique to display any other code which is supported by your target browser, such as Javascript or
Java applets.
Saba also provides a powerful library of widgets which you can use in your pages and portlets. There are several
advantages to using widgets:

The widgets provide a wide range of functionality, including special tools for accessing the Saba data store.
The Saba application automatically adjusts the widgets to match whatever theme and skin is in use.
Saba provides a syntax for dynamically creating as many widgets as necessary for the data you want to display. For
example, you might create a page which displays all students in a class, along with a "delete student" button for each
student. In that case, you could define the "delete student" widget once, then use WDK directives to instruct Saba
to create a copy of that widget for each row in the "students enrolled" table.

This section of the tutorial describes how to add a widget to the "Hello World" portlet defined in the previous section.
In this case, the portlet has a single widget, a textArea widget which can be used to display or input text. The following
section describes how to create multiple widgets dynamically to match whatever text your application page may be
displaying.
If you use a widget, you must insert code in two places:

The model file defines the widget itself, specifying what parameters are passed to the widget and how it should be
displayed.
The view file specifies where in the file the widget should be displayed.

Model File with Widget


This version of the "Hello, World" portlet displays the text with an input widget. This widget shows a text entry field,
with whatever label you define. The portlet initializes the field with the "Hello, world" message and marks the widget
as view-only.
Widgets are defined in the <wdk:widgets> area of the model file. This version of the model file is exactly like the
one in the previous section, except for that node. (Once again, we assume that the resource bundle and label specified
evaluate to some appropriate text, like "Hello, world!")

<?xml version="1.0"?>

<xsp:page language="java"

| WDK Page Tutorial | 36

xmlns:xsp="http://www.apache.org/1999/XSP/Core"
xmlns:wdktags="http://www.saba.com/XML/WDK/taglib">

<xsp:structure>

<xsp:include>com.saba.*</xsp:include>

</xsp:structure>

<wdk:page xmlns:wdk="http://www.saba.com/XML/WDK">

<!-- the head element contains standard header data


for all WDK model files -->

<wdk:head>

<!-- No input or output parameters for this portlet. -->

<!-- the title of the page or portlet -->

<wdk:title>Hello World</wdk:title>

<!-- Load the resource bundle... -->

<wdktags:i18n.load resource="my_custom_pages"/>

<!-- WDK labels, usable by the view file -->

<wdk:labels>
<!-- Label is no longer needed! -->

</wdk:head>

<wdk:form method="POST">

</wdk:labels>

| WDK Page Tutorial | 37

<!-- Model information is in this tag. However, this portlet has no


information here.-->

<wdk:model>

<!-- No model information needed. -->

</wdk:model>

</wdk:form>

<!-- Widgets are defined here. -->

<wdk:widgets>

<wdk:input name="helloText">

name="kI18n511111helloWorldLabel"/></value>
</wdk:input> </wdk:widgets>

<value><wdktags:i18n.label
<viewOnly>true</viewOnly>

</wdk:page>

</xsp:page>
As you can see, this version of the portlet no longer has a label. The view file doesn't display the text directly, by accessing
the label. Instead, it displays the helloTextwidget, which contains the message.

View File with Widget


In the earlier version of the "Hello World" portlet, the view file retrieved the text string from the model file, then displayed
that text as HTML. In the new version of the portlet, the view file does not access the text directly. Instead, it contains
a directive to display the widget which contains the text.

<?xml version="1.0" encoding="UTF-8"?>

<?wdklint ignoreRules="XSL_HTML"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"


xmlns:wdk="http://www.saba.com/XML/WDK"

| WDK Page Tutorial | 38

xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rss10="http://purl.org/rss/1.0/">

<xsl:import
href="/wdk/xsl/view/wdk_defaultview.xsl"/>

<xsl:import
href="/wdk/xsl/view/wdk_widgetlayout.xsl"/>

<xsl:template match="wdk:model">

<!-- Portlet contents should be put inside an HTML table


or a table widget. --><table width="100%" height="100%" border="0"
cellspacing="1" cellpadding="0"><tr><td>
<xsl:apply-templates
select="$wdkWidget[@name='helloText']"/>
</td></tr>

</table>

</xsl:template>

</xsl:stylesheet>
The bold-faced statement applies the standard Saba widget library's stylesheet to the selected widget. In this case, the
widget is identified by its name, but you could identify the widget by any other XSLT path. For example, you could
search for a widget with a particular ID, or for a widget that's the child of some other node.

Command Objects and Visitors


Unlike application pages that display static content, dynamic pages display information retrieved from the Saba data
store. In some circumstances, you do not even know how much data the page displays. For example, a page written to
display all the goals assigned to students in a class displays no goals if the class has no students or the students have no
goals. If the students have goals, the page can display pages of information.
The WDK enables you to write application pages that dynamically generate their content by executing Java code that
invokes a command object.
A model file is not transformed directly into the application page that users see. Instead, the model file is compiled once
into a special Java class. Then, whenever a user views that application page or portlet, the Java object is created, and it
generates an XML document. The view file, in turn, transforms that created XML document into the application page
or portlet that is shown to the user.
A model file can contain one or more <wdktags:execute> tags. When the model file is compiled, these tags are
turned into Java code that creates a specified Java object, and invokes that object's execute method. That method is
responsible for returning a well-formed XML node. When the model file's object is executed, the <wdktags:execute>
directive is replaced by whatever code is output by the command object.

| WDK Page Tutorial | 39

This example uses a simple command object to generate the XML node. In this example, the command object generates
a static, fixed node, containing data which is displayed in the application page. Of course, the true strength of command
objects is that they can generate their XML output dynamically, and can retrieve information from the Saba data store.
In addition, this application page is designed as a stand-alone page, instead of as a portlet. There is very little difference
between writing a portlet and writing an application page. However, you must add the application page to the Saba
menus, instead of providing a portlet that users can add to their portal pages. For more information about adding new
application pages into the menu system, refer to the System Administrator Guide.

The Control File


The control file for this application page is much like the control file for the portlets described earlier in the tutorial.

<?xml version="1.0" encoding="UTF-8"?>

<?cocoon-process type="wdk"?> <!-- A standalone page, not a portlet-->

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:wdk="http://www.saba.com/XML/WDK">

<!-- Saba reserves IDs up to 500000 -->

<rdf:Description id="522222">

<rdf:type resource="http://www.saba.com/XML/WDK/Control"/>

<wdk:version>1.0</wdk:version> <wdk:model
rdf:resource="tutorialCommandPage.xml"/> <wdk:view
rdf:resource="tutorialCommandPage.xsl"/> <wdk:widgets
rdf:resource="/wdk/xsl/widget/wdk_widgets.xsl"/>

</rdf:Description>

</rdf:RDF>
In this case, we assume that the control file is in the same directory as the model and view files, and that all three files
have the root name tutorialCommandPage (with the various appropriate file extensions). We give this application
page the ID number 522222, and designate it as a standalone application page, instead of a portlet.

The Command Object


The core of this new application page is its command object. A model file can run Java code by including a
<wdktags:execute> directive, which is compiled into an instruction to create a particular object and run a particular
method in that object. The <wdktags:execute> directive offers several different calling methods. This example

| WDK Page Tutorial | 40

uses the simplest version of that directive, in which it specifies that a particular command object should be created, and
that command object's execute method should be invoked.
A command object must implement Saba's ICommand interface.Usually, it does this by extending the
AbstractCommand class. If you create a new class based on AbstractCommand, you must provide two methods:

You must write a no-argument constructor for the object. If the command object expects to be passed parameters,
the constructor method performs some initial processing. If the command object doesn't pass or return parameters,
you can write an empty constructor (as in this example).
You must write the doExecute method. This method is what generates the XML output. The method does this by
using a visitor. The visitor is a special-purpose object created by AbstractCommand's code, which produces XML
representations of Java objects. If the visitor examines a simple Java type (like a number or a string), it generates an
appropriate XML node. If it visits Saba object which implements IXMLObject, it generates a node for the object,
then recursively visits each field in the object. Thus, if you want to generate an XML representation of some Saba
data, all you usually need to do is create the appropriate Saba object, then pass it to the XML visitor.

| WDK Page Tutorial | 41

In the case of this example, the command object builds its XML output a node at a time, creating a static data set.

/*

* TutorialCommand.java

*/

package com.mycorp.tutorial

import java.util.*;

import javax.servlet.http.HttpServletRequest;

import com.saba.web.dk.AbstractCommand;

import com.saba.xml.IXMLVisitor;

import com.saba.xml.SabaXMLType;

/**

* A test command object. It is not passed any parameters. It

* generates the XML output manually.

*/

public class TutorialCommand extends AbstractCommand

| WDK Page Tutorial | 42

/** Default constructor; doesn't need any code

*/

public TutorialCommand()

/* This command object doesn't need any initialization. */

/**

* This command generates the output. In this case, it does so

* by using the IXMLVisitor to manually build the output, node

* by node.

* This example generates output with the format

<people>

<person>

<last_name>Smith</last_name>

<first_name>John</first_name>

</person>

| WDK Page Tutorial | 43

<!-- ...other 'person' nodes...-->

</people>

*/

public void doExecute( HttpServletRequest request,

IXMLVisitor visitor )

throws Exception

// Build the XML output node by node.

visitor.beginVisit(null, "people", null, null, null);

visitor.beginVisit(null, "person", null, null, null);

visitor.visit(null, "last_name", "Malfoy");

visitor.visit(null, "first_name", "Draco");

visitor.endVisit(null, "person");

visitor.beginVisit(null, "person", null, null, null);

visitor.visit(null, "last_name", "Dumbledore");

visitor.visit(null, "first_name", "Albus");

| WDK Page Tutorial | 44

visitor.endVisit(null, "person");

visitor.beginVisit(null, "person", null, null, null);

visitor.visit(null, "last_name", "Weasley");

visitor.visit(null, "first_name", "George");

visitor.endVisit(null, "person");

visitor.endVisit(null, "people");

}
This object must be compiled, and the class file placed in your application server's class path.

Model File with <wdktags:execute>


The model file for this example is fairly simple. It is much like the model files used by the portlets earlier in the tutorial.
It has two significant differences:

Instead of containing the data directly in the file, the model file invokes a command object to generate XML data.
(It uses the command object defined in the last section, TutorialCommand.)
The model file uses a table widget to display the XML data. The model file attaches the table to the data generated
by the command object, by using a <wdktags:attachTo> directive. The table widget automatically displays as
many rows as are needed by the data.

<?xml version="1.0"?>

<xsp:page language="java"

| WDK Page Tutorial | 45

xmlns:xsp="http://www.apache.org/1999/XSP/Core"
xmlns:wdktags="http://www.saba.com/XML/WDK/taglib">

<xsp:structure>

<xsp:include>com.saba.*</xsp:include>

</xsp:structure>

<wdk:page xmlns:wdk="http://www.saba.com/XML/WDK">

<!-- the head element contains standard header data


for all WDK model files -->

<wdk:head>

<!-- No input or output parameters for this portlet. -->

<!-- the title of the page or portlet -->

<wdk:title>Test of Command Object</wdk:title>

<!-- Load the resource bundle... -->

<wdktags:i18n.load resource="my_custom_pages"/>

<!-- WDK labels, usable by the view file -->

<wdk:labels /> <!-- No labels needed -->

</wdk:head>

<wdk:form method="POST">

<!-- Model information is in this tag. This is where we make the call to

| WDK Page Tutorial | 46

the command object. -->

<wdk:model>
<!-- In the output, the following tag is replaced
by
the XML node output from the command object.
-->
<wdktags:execute
commandObj="com.mycorp.tutorial.TutorialCommand"

/>

</wdk:model>

</wdk:form>

<!-- Widgets are defined here. -->

<wdk:widgets>
<wdk:table name="peopleTable">
<!-- The attachTo node
indicates that this widget is
linked to the 'people' node created by the
command object. -->
<wdktags:attachTo path="people"/>
<head> <!-Defines the heading row -->
<!-- Usually, you would use internationalizable labels for the heading row's labels.
In this example, we use
static text for
simplicity. -->
<column>First Name</column>
<column>Last Name</column>
</head>
<row path="person"> <!-- i.e. one
row for each
'person' node -->
<column>
<wdktags:nodeRef path="first_name"/>
</column>
<column>
<wdktags:nodeRef path="last_name"/>
</column>
</row>
</wdk:table>
</wdk:widgets>

</wdk:page>

</xsp:page>

| WDK Page Tutorial | 47

The <wdktags:execute> directive invokes the command object; as we saw, that object generates the static XML
output

<people>

<person>

<last_name>Malfoy</last_name>

<first_name>Draco</first_name>

</person>

<person>

<last_name>Dumbledore</last_name>

<first_name>Albus</first_name>

</person>

<person>

<last_name>Weasley</last_name>

<first_name>George</first_name>

</person>

<people>
The model file also specifies that a table widget should be created, and attached to the <people> node. The table
widget specifies that it should have one row for each <person> node under the <people> node. Each row, in turn,
has one column for the first name and one for the last name. The widget thus generates a table like this:
First Name

Last Name

Draco

Malfoy

| WDK Page Tutorial | 48

First Name

Last Name

Albus

Dumbledore

George

Weasley

Note that the table displays first name before last name, which is different from the order in the command object's output.
When you define the table widget, you are perfectly free to specify what order you want the data to appear in. You can
also choose to omit some of the data returned by your command object. For example, you could easily create a table
which displayed last names.

View File
This view file is very straightforward. All the model file's data is presented in a table widget. The view file needs to
display that widget.

<?xml version="1.0" encoding="UTF-8"?>

<?wdklint ignoreRules="XSL_HTML"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"


xmlns:wdk="http://www.saba.com/XML/WDK"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rss10="http://purl.org/rss/1.0/">

<xsl:import
href="/wdk/xsl/view/wdk_defaultview.xsl"/>

<xsl:import
href="/wdk/xsl/view/wdk_widgetlayout.xsl"/>

<xsl:template match="wdk:model">
<xsl:apply-templates
select="$wdkWidget[@name='peopleTable']"/> </xsl:template>

</xsl:stylesheet>

Chapter

4
WDK Widgets
Topics:

Widget Overview
Using Widgets
Widget Format
Widgets and Model Objects
The Standard Widget Library
<wdk:componentWidget>
<wdk:customFields>
<wdk:dateInput>
<wdk:deleteLink>
<wdk:domainPicker>
<wdk:file>
<wdk:finderQuery>
<wdk:finderResult>
<wdk:finderControl>
<wdk:form>
<wdk:frameworkLink>
<wdk:functionality>
<wdk:genericText>
<wdk:hiddenField>
<wdk:impresence>
<wdk:input>
<wdk:impresence>
<wdk:link>
<wdk:list>
<wdk:lovPicker>
<wdk:multistep>
<wdk:multisection>
<wdk:pageText>
<wdk:pageTitle>
<wdk:pageWidget>
<wdk:parameters>
<wdk:presence>
<wdk:promptForSave>
<wdk:sabaPicker>
<wdk:saveLink>

This chapter describes how to use UI widgets in Saba WDK application pages.
The chapter also includes a full reference of available widgets.

| WDK Widgets | 50

<wdk:script>
<wdk:table>
<wdk:textarea>
<wdk:timeInput>
<wdk:tree>
<wdk:validatorWidget>
Component Dictionary Support
Example

| WDK Widgets | 51

Widget Overview
WDK application pages do not code UI elements (such as menus and buttons) directly. Instead, application pages use
widgets. Widgets are defined in terms of their functionality. For example, instead of using a "text field", a WDK page
uses an input widget. The WDK engine then translates the widget into an appropriate representation for the desired
output format. If the application page is being rendered in HTML, the WDK engine generates appropriate HTML
components. But if, in some future release, other formats are supported (such as WML), the WDK engine could render
the widgets in an appropriate way for that format.
Widgets are used in this way in the WDK:

The model file defines which widgets are available. It specifies all the contents and attributes of the widgets (such
as the widgets' names and labels). The contents are often set dynamically. For example, a model page might use the
results of an <wdktags:execute> tag to set the contents of a list widget.
If a WDK page has a model object, Saba queries it for each widget to find out whether the widget is visible. It also
checks to see if the widget is editable and if it should be initialized from the HTTP request that launched the page,
if that is not explicitly specified in the model file.
The view file specifies how widgets are arranged on the page. It does not need to include all the widgets defined on
the model page. The model might provide a range of widgets, letting the view page choose those that are appropriate.
The control file, besides associating a particular view with a particular model, also specifies an appropriate widget
library file. This file is used by the WDK engine to transform the model page's widget tags into appropriate code
for the target language.

Currently, the only widget library provided is the master widgets file wdk_widgets.xsl. (This master file, in turn,
loads other library files providing various widgets.) This library transforms widgets into HTML UI components. The
widget library is specified in the control file with a <wdk:widgets> tag, which is a child of the <rdf:Description>
tag:

<wdk:widgets rdf:resource="/wdk/xsl/widget/wdk_widgets.xsl"/>
You add widgets to an application page by adding XML code to the model and view pages. Each widgtet's XML tag
format is defined by an XSLT file. The various widget XSLT files are all included in the library's master WDK widgets
file (wdk_widgets.xsl). This is the file you specify in the application page's control file.

Using Widgets
As noted, if you are using a widget in an application page, you must edit two different files:

In the model file, you place a full description of the widget. This is an xml node that describes the widget's appearance,
behavior, and so on. The widget may be given a name, and may be attached to a particular XML node.
In the view file, you put an XML tag specifying where the widget goes. The widget might be identified by its location
(for example, "every link that is attached to a <person> node").

Some widgets can contain other widgets. For example, you might use the table widget to create a table, each of whose
rows has a button (created with the link widget). In that case, you include the container widget in the view file. Saba
builds that widget with all of its contents.

| WDK Widgets | 52

Defining in the Model File


Widgets are defined in the <wdk:widgets> node of the model file (described in Chapter 5, "Model Objects"Chapter
5, "Model Objects"). Each widget may contain special directives that create a connection between the widget itself and
some part of the model. For example, you may want to have an input field whose initial value is determined by some
component of the data model you produced. The <wdktags:nodeRef><wdktags:nodeRef> directive enables
you to access the value of an XML node in the data model.

| WDK Widgets | 53

You may also want to associate widgets with data elements in the model object. For example, a particular page might
display a list of people. You might want to create a widget (such as an edit button) for each person in the list. In a
situation like this, the data model produced within <wdk:model> might contain a list of person nodes, as follows:
<wdk:model>

<people>

<person>

<name>Sam Malone</name>

<age>40</age>

<id>persn00001</id>

</person>

<person>

<name>Frasier Crane</name>

<age>38</age>

<id>persn00002</id>

</person>

</people>

</wdk:model>
This model represents the data we have about people. It does not specify how we interact with this data. However, we
can associate an edit button with each person, so when the button is clicked the edit page is shown for the given person.

| WDK Widgets | 54

To do this, define a single widget and specify that the widget be copied and attached to each individual <person>
node:

<wdk:widgets>

<wdk:link name="editButton">

<wdktags:attachTo path="/people/person"/> <!-- This attaches the widget to


every "person"

node -->

<href>editPage.rdf</href>

<label>Edit</label>

<prompt>Edit name: <wdktags:nodeRef path="name"/></prompt>

<field>

<name>personId</name>

<value><wdktags:nodeRef path="id"/><!-- This retrieves the ID value from


the "person"

node --></value>

</field>

</wdk:link>

</wdk:widgets>
The <wdktags:attachTo><wdktags:attachTo> tag specifies that a copy of the button should be attached to
each <person> node that is a child of the <wdk:model><people> node. The generated XML page contains the
following content:

<wdk:form>

| WDK Widgets | 55

<wdk:model>

<people>

<person>

<name>Sam Malone</name>

<age>40</age>

<id>persn00001</id>

<wdk:link name="editButton">

<href>editPage.rdf</href>

<label>Edit</label>

<prompt>Edit name: Sam Malone</prompt>

<field>

<name>personId</name>

<value>persn00001</value>

</field>

</wdk:link>

</person>

<person>

| WDK Widgets | 56

<name>Frasier Crane</name>

<age>38</age>

<id>persn00002</id>

<wdk:link name="editButton">

<href>editPage.rdf</href>

<label>Edit</label>

<prompt>Edit name: Frasier Crane</prompt>

<field>

<name>personId</name>

<value>persn00002</value>

</field>

</wdk:link>

</person>

</people>

</wdk:model>

</wdk:form>

<wdk:widgets>

| WDK Widgets | 57

</wdk:widgets>
Note the following:

The <wdktags:attachTo> directive creates a copy of the widget under each model node selected by the path
attribute. Notice that in this case two <wdk:link> elements were created, one inside each person element. This is
because the <wdktags:attachTo> directive attached the widget to every <person> node.
The <wdktags:nodeRef> directive retrieves the value of sub-nodes of the node the widget is attached to. In the
above example this directive was used to generate a dynamic prompt (to include the user's name in the prompt) and
a dynamic field value (to use the id of the person). This latter use is similar to how runtime variables were used in
the .saba pages used by earlier versions of Saba.
All of the generated links have the same name attribute, "editButton". The name is copied from the original
version of the widget (in the <wdk:widgets> section). This is not a problem, since each widget can be identified
by an XML path that combines its location and name.

The <wdk:widgets> section became empty because the widget (the <wdk:link> ) has been copied to the model
nodes. However, if a widget does not use the <wdktags:attachTo> directive, then that widget
remains inside the <wdk:widgets> element and remain accessible by the widget and view transformations.

Including in the View File


In order for a widget to appear in the application page, it must be included in the view file. If a widget is attached to a
particular model node, as in the above example, use the following element:

<xsl:apply-templates select="wdk:widget[@name='widget_name']"/>
This node is usually a child of an appropriate <xsl:foreach> or something similar, which identifies the location of
the widget in the generated model file.
For example, suppose the model file were the one described above, which attached an edit link to each <person>
node. You could generate a list of people, with each user's name and an edit button, by putting the following code in
the view file:

<xsl:for-each select="people/person">

<xsl:value-of select="name"/> <!-- Displays user's name -->

<xsl:apply-templates select="wdk:widget[@name='editButton']"/>

<BR />

</xsl:for-each>
The <xsl:for-each> directive iterates through each <person> node under the <people> node. The
<xsl:apply-templates> is applied, in turn, to each <person>. It displays the widget under that node whose
name is 'editButton'.

| WDK Widgets | 58

If a widget is not attached to a particular model node, you can display it with the following element:

<xsl:apply-templates select="$wdkWidget[@name='widget_name']"/>

Widget Format
This section describes the format of a widget declaration in the model page. It also describes some of the common
attributes and child nodes that are available for most or all widgets. The specific attributes and children available to each
widget are described in "The Standard Widget Library".
You declare a widget by adding a child to the model file's <wdk:widgets> node. The widget looks something like
this:

<wdk:widget_name attribute="..." attribute="...">


<child_node/><child_node/><child_node/> <!-- ... -->

</wdk:widget_name>
The specific contents of the widget vary from one widget to the next. However, there are several attributes and child
nodes that you usually see repeated from one widget to the next.

'name' attribute
This string is used to identify the widget. Every widget must have a name. However, the name does not have to be unique
on the model page. You can have several widgets with the same name, even if they are children of the same node.
As described above, when a widget has an <wdktags:attachTo> child, it is duplicated as necessary and made a
child of every qualifying node. This can result in many widgets with the same name, but in different locations.

<id>
Many widgets can have an <id> node. This node contains a text string that is used as the widget's identifier in several
circumstances. For example, if a widget generates an HTML form element, the <id> is often used as the name of the
element, or as the root of that name. For details on how each widget uses its <id> node, see the widget's reference
section.
Note: <id> is usually an optional element. If you do not specify an <id> for a widget, Saba uses the widget's
name as an ID. However, if a widget is being duplicated and attached to many data nodes, you may want to explicitly
specify an ID for each widget, by basing it on the data the widget is attached to. An example of this is shown in
"Defining in the Model File".

<data>
Many widget elements have <data> subelements, which are used to generate data to attach to a particular part of the
widget. For example, tree nodes have <data> subelements that specify what data to display in that node. In most cases,

| WDK Widgets | 59

the <data> subelements follow a common format. (If a widget uses <data> nodes differently, that is noted in the
widget's documentation.)
A <data> element may contain any of the following

<wdk:link>, <wdk:frameworkLink>, <wdk:list>, or <wdk:textarea> widgets


Any free-flow text or standard HTML tags (such as <p> or <br>) are passed through unchanged

<wdktags:attachTo>
This tag attaches the parent widget to the model node or nodes designated by the path and root attributes. The model
node are identified by following the path from the <wdk:model> node or from the node specified by the root attribute.
If more than one node matches the path, the widget is duplicated, and a copy is attached to each matching node. Once
the widget has been attached to a model node, elements in the widget can use data from that node with the
<wdktags:nodeRef> tag (described in "<wdktags:nodeRef>""<wdktags:nodeRef>" on page 3-34).
The <wdktags:attachTo> tag has the following attributes:
Table 3-20:) Properties of<wdktags:attachTo> Tag
Name

Required

Value

path

No

An Xpath expression identifying a node in the model section. The widget is attached
to that node. (This is a relative path, with the root attribute as the path's root.)
Either path or pathExpr must be specified.

pathExpr

No

A Java expression that evaluates to an XPath expression specifying the model node
to attach to.
Either path or pathExpr must be specified.

root

No

The root node of the path. The default is the node corresponding to the
<wdk:model> node in the model page.

| WDK Widgets | 60

The following example defines an input field called Name and attaches it to the <leaf> element in the <model>
node:

<wdk:input name="Name">

<wdktags:attachTo path="/leaf" root="model"/>

<label><wdktags:i18n.label name="kI18n9001Name"/></label>

<id>name</id>

<value><wdktags:nodeRef path="name"/></value>

</wdk:input>
When the model file is processed, the <wdktags:nodeRef> tag is replaced with the contents of the
model/leaf/name node.

<initializeFromRequest>
If this node is present, the widget should try to initialize itself from the request object that generated the application
page. This is only useful in certain limited circumstances.
Certain operations on an application page can force the entire page to be reloaded. For example, if you change the domain
choice with a domain picker, that can force Saba to regenerate and redisplay the page. When this happens, you usually
want all the other form elements on the page to keep whatever values the user may have entered.
The ordinary way to arrange this is to pass parameters to the successor page, containing all the data the page needs to
repaint itself and properly initialize all widgets. However, in some circumstances, this may not be feasible.
The <initializeFromRequest> node takes advantage of the fact that when the HTTP request is made, all the
contents of the form elements are passed along as part of the request. If a page is reloading itself, Saba can properly set
up the form elements by extracting the information from the request object.
To indicate that a widget should be initialized from the request object, add an <initializeFromRequest> node.
The node has no attributes or children. Its contents must be either:
Table 3-20:) Contents of <initializeFromRequest>Tag
Value

Meaning

true

The form element should be initialized from the request object. This overrides any other nodes that
may specify a value for the form element.

false

The form element should not be initialized from the request object. (This is the default behavior.)

If a widget is marked "initialize from request", and the page is not being reloaded, the behavior is undefined. In that
situation, the request object is generated by a completely different page. Saba tries to find a value for the form element

| WDK Widgets | 61

in that request object. Most likely it fails, and the element is treated as if <initializeFromRequest> were set to
<false>. However, Saba may find inappropriate data in the request object and initialize the element improperly.
For this reason, the contents of this tag should not usually be a simple, hardcoded true or false string. Instead, if
you use this tag at all, you should use an XML tag or Java expression that evaluates to true or false as appropriate.
If the application page has a model object, Saba queries that object for each widget that does not contain an explicit
<initializeFromRequest> node, and finds out if the widget should be initialized from the request object. This
is described further in "Widgets and Model Objects".

<viewOnly>
If this node is present and contains the value true, the widget appears in a view-only form. The way this is done varies
depending on the widget. Acceptable values for the node include:
Table 3-20:)Contents of<viewOnly>Tag
Value

Meaning

true

The widget should be displayed in a view-only form.

false

The widget should be displayed in an editable form, if appropriate for the widget type. (This is the
default behavior.)

If the application page has a model object, Saba queries that object for each widget that does not contain an explicit
<viewOnly> node, and find out if the widget should be displayed as view-only. This is described further in "Widgets
and Model Objects".

Widgets and Model Objects


One of the functons a model object can perform is to manage the state of the widgets. When Saba builds an application
page, it examines each widget in turn to decide how the widget should be displayed. It follows these steps for each
widget:
1. If the page has a model object, Saba queries that object to find out if the widget should be displayed at all.
Note: If the model object says the widget should not be displayed, Saba does not follow the remaining steps.
It also does not display any widgets that might be contained within that widget.
2. Saba examines the widget declaration to see if it has an <initializeFromRequest> tag (described in
"<initializeFromRequest>"). If the widget has such a tag, with a value of either true or false, Saba obeys that
tag. If the widget does not have this tag, but the page has a model object, Saba queries the object to find out if the
widget should be initialized from the request object. If the widget does not have such a tag and the page has no model
object, the widget is not initialized from the request object.
3. Saba examines the widget declaration to see if it has an <viewOnly> tag (described in "<viewOnly>"). If the
widget has such a tag, with a value of either true or false, Saba obeys that tag. If the widget does not have this
tag, but the page has a model object, Saba queries the object to find out if the widget should be made view-only. If
the widget does not have such a tag and the page has no model object, the widget is not made view-only.
For more information about how the model object handles these queries, see "Managing Widgets""Managing Widgets"
on page 5-6.

| WDK Widgets | 62

The Standard Widget Library


The following widgets are provided by the Saba Cloud WDK:

| WDK Widgets | 63

Table 3-20:)Widgets Defined in Widget Library


Widget Name
<wdk:attachments>

<wdk:breadCrumb>

Description
Lets the user view and manage an object's attachments (files and hyperlinks).

Displays a "breadcrumb trail"-a sequence of hyperlinks showing the navigation path


a user followed to reach the current page.

<wdk:chart>

Displays a chart (pie chart, bar chart, and so on.).

<wdk:chartToolbar>

Displays controls for managing a <wdk:chart>.

<wdk:componentPick~
er>

<wdk:componentWid~
get>

A special-purpose <wdk:sabaPicker> used for selecting a Saba component.

Displays the fields of a widget.

<wdk:customFields>

Generates display/input fields for a Saba object's custom fields.

<wdk:dateInput>

A date field and the associated date picker.

<wdk:deleteLink>

A special-purpose <wdk:link> used to create "delete" links.

<wdk:domainPicker>

A special-purpose <wdk:sabaPicker> used for atomically changing the domain.

<wdk:file>

A file upload widget.

<wdk:finderQuery>

Generates a finder on the page, letting users run searches.

<wdk:finderResult>

Displays the results of a finder search.

| WDK Widgets | 64

Widget Name

Description

<wdk:finderControl>

Generates finder control elements.

<wdk:form>

A form element.

<wdk:frameworkLink>

Special-purpose link to the site's home page.

<wdk:functionality>

Designates that widgets belong to a Saba functional area, and should be turned on
or off depending on whether that functionality is enabled.

<wdk:genericText>

Text entry field. Depending on the maximum size, Saba displays it as a single-line
input box or a multi-line text area.

<wdk:hiddenField>

A hidden element.

<wdk:input>

A password or single line text input.

<wdk:link>

<wdk:list>

<wdk:lovPicker>

Links and buttons. One click can execute a sequence of actions, can invoke .saba
and WDK pages, and can invoke popup windows.

Drop-down, checkbox and radio button widgets.

A combination of a drop-down menu and a popup window. Users can launch the
popup window to select an appropriate value, which is selected in the menu.

<wdk:pageText>

Includes page-specific text from the page text resource bundle.

<wdk:pageTitle>

Specifies an application page's title and other properties of the application page.

<wdk:pageWidget>

Includes another WDK page in the current page.

<wdk:parameters>

Displays the request parameters.

| WDK Widgets | 65

Widget Name

Description

<wdk:presence>

Works with the SabaDialog code to graphically display whether a particular user is
available in real-time.

<wdk:promptForSave>

Monitors to see if a user tries to leave the page without saving his or her work. If
the user does, the widget pops up a dialog box asking if he or she means to do that.

<wdk:sabaPicker>

A combination of a text entry field and a popup window. Users can launch the popup
window to select an appropriate value, which is placed in the text field.

<wdk:sabaPick~
er><wdk:sabaPicker>

A widget for invoking the standard saba picker popup windows.

<wdk:saveLink>

A special-purpose <wdk:frameworkLink> used to create "save" links.

<wdk:script>

Inserts a JavaScript expression or function call.

<wdk:table>

A widget for defining a table based on data in the <wdk:model> section.

<wdk:table>

A widget for defining a table based on data in the <wdk:model> section.

<wdk:textarea>

A multiline text input field.

<wdk:timeInput>

A time field and the associated time picker.

<wdk:tree>

Displays a tree, used to show hierarchical data.

<wdk:validatorWid~
get>

A widget to make sure the user enters proper information in all "required" widgets.

The following sections describe the widgets provided by the standard library, and describes the way they behave when
transformed into HTML/Javascript by the WDK engine.

| WDK Widgets | 66

<wdk:attachments>
Used to add an Attachments widget to a page. This lets the page add, view, or modify file or URL attachments to a
Saba object that supports them.
The <wdk:attachments> tag has one attribute:
Table 3-20:)Attributes of<wdk:attachments> Tag
Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

It has the following child elements:


Table 3-20:)Child Elements of <wdk:attachments> Tag
Child Element

Required

Value

Yes

The ID of the object that owns the attachments.

Yes

What to do when the Delete or Save icon is clicked in the Add


Attachment or Attachment Details popup window. This

attachmentOwnerId

onAction

| WDK Widgets | 67

Child Element

Required

Value
node has a single child, <mainpage>, which contains the
fully qualified .rdf file of the application page to load.

No

If true, the widget enables users to view attachments, but


not to add, edit, or delete them. Defaults to false (users can
edit attachments).

No

Used to override the default title for the table. The default title
for the table is 'Attachments'.

No

Used to show/hide the category column. Defaults to true.

No

Used to show/hide the Type column. Defaults to true.

No

Used to show/hide the Locale column. Defaults to true.

No

Used to show/hide the Private column. Defaults to true.

No

Indicates whether private attachments should be shown.

viewOnly

title

showCategory

showType

showLocale

showPrivate

displayPrivateAttachments

The default behavior of this property is derived from whether


the System object has the "Can View Private Attachments"
privilege. However, this element can be used to explicitly
override that privilege setting on a specific widget.

No

Used for the main page to specify its own javascript function
for the attachment popup to call when it closes. This allows
the main page to pass in any parameters needed for refreshing
itself.

No

If false, the export link is not shown and table contents


cannot be exported. Defaults to true.

No

If false, the print link is not shown and table contents cannot
be printed. Defaults to true.

callbackFn

export

print

| WDK Widgets | 68

Example

<wdk:attachments name="attachmentsTable">

<title>My Attachments Widget</title>

<attachmentOwnerId><xsp:expr>id</xsp:expr></attachmentOwnerId>

<onAction>

<mainPage>/common/party/organization/editOrganizationInternal.rdf<

/mainPage>

</onAction>

<viewOnly>true</viewOnly>

<callbackFn>javascriptFunctionName</callbackFn>

</wdk:attachments>

<wdk:breadCrumb>
Used to add a "breadcrumb" to a page (showing the route of application pages the user followed to reach this page, and
linking to the earlier pages).
Note that this widget must be placed before the <wdk:pageTitle> tag, in order for the bread crumbs to be properly
displayed as part of the page title.
The <wdk:breadcrumb> tag has one attribute:

| WDK Widgets | 69

Table 3-20:)Attributes of <wdk:breadCrumb>Tag


Attribute Name

name

Required

Yes

Value

The name of the widget. The view stylesheet uses


this name to identify the widget. For example,

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

It has the following child elements:


Table 3-20:)Child Elements of <wdk:breadCrumb>Tag
Child Element

Required

Value

No

The name of the generated form element. If this is not specified, the value
of the widget's name attribute is used.

No

The maximum length for each label, in number of characters. Default is


15.

No

The last label to display in the breadcrumb trail. The default value is the
name of the application page.

No

Used to specify the previous link or links in the breadcrumb trail. You
may have as many <link> nodes as you like. They are specified in order,
from first ("top") page in the breadcrumb trail to last. This element has
required subelements, which are described in "<link> Child Elements".

id

maxChar

lastLabel

link

<link> Child Elements


The breadcrumb widget has one <link> widget for each application page in the breadcrumb trail. Each <link> has
the following child elements:

| WDK Widgets | 70

Table 3-20:)Child Elements of <wdk:breadCrumb><link>Tag


Child Element

Required

Value

Yes

Page to load when this link is clicked.

Yes

Label for the link.

No

Text prompt for the link. If not specified, the label is used.

No

A parameter to pass to the called page when the link is clicked. You may
have as many or as few <field> tags as you wish for each <link>.
Every <field> must have two children: a <name> with the parameter's
name, and a <value> with the parameter's value.

href

label

prompt

field

<wdk:chart>
Displays numeric data as a chart. You may display the data in several formats (pie chart, bar chart, and so on). In many
cases, you can choose between a "flat" version of a chart and a "2.5-dimensional" version. The 2.5-dimensional version
displays the same data as the flat version, but the chart elements are given a raised, three-dimensional appearance. The
data for the chart can be provided in the widget XML, or you can specify a Java object that contains the chart data.
The chart widget can display the following kinds of charts:

| WDK Widgets | 71

Table 3-20:) <wdk:chart>Chart Types.


Chart Type

area

Description
An area chart. That is, each data series is displayed as a line, with the area underneath it
colored. If there is more than one data series, the series are stacked, one on top of the other.
The value for any given series is the distance between its line and the line below it.

bar

A bar chart. That is, each data series is displayed as a series of horizontal bars, with the
width of the bar showing the data magnitude.

bar25d

As bar, except that the bars are given a raised, three-dimensional appearance. This is the
default chart type.

column

A column chart. That is, each data series is displayed as a series of vertical columns, with
the height of the column showing the data magnitude.

As column, except that the columns are given a raised, three-dimensional appearance.
column25d

A line chart. That is, each data series is displayed as a horizontal line.
line

pie

A pie chart. That is, a single data series is displayed as portions of a circle. If more than
one data series is passed, the first is displayed, and the others are ignored.

As pie, except that the pie is given a raised, three-dimensional appearance.


pie25d

stackedcolumn

stacked~
column25d

A stacked-column chart. That is, each data series is displayed as a single column, made up
of differently-colored regions representing each data point in that series.

As stackedcolumn, except that the columns are given a raised, three-dimensional ap~
pearance.

When Saba builds the chart, it checks to see if the page was passed a parameter with the name <chart_id>_type. If
so, Saba uses the value of that parameter as the chart data type. (As always, if the chart widget's id is not explicitly

| WDK Widgets | 72

specified, Saba uses the chart name as the id. In this case, Saba looks for a parameter with the name
<chart_name>_type.)
If there is no parameter with the appropriate name, Saba checks to see if the widget has a <type> child. If so, Saba
uses the value of that node as the chart type. If the chart is not passed a <chart_id>_type parameter and does not have
a <type> node, Saba displays it as a bar25d chart.
You can also create a <wdk:chartToolbar> widget, which adds controls to let users change the chart's appearance
(for example, by changing the type of chart displayed).
The <wdk:chart> tag has one attribute:
Table 3-20)Attributes of <wdk:chart> Tag
Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='yourWidgetName']"/>
It has the following child elements, regardless of whether the chart data is specified directly in the XML or with a Java
object:
Table 3-20:)Child Elements of <wdk:chart>Tag
Child Element

Required

Value

No

A Java expression that evaluates to a ChartData object. If this


node is present, Saba uses the ChartData to set the chart's labels,
data series, and so on. If the node is not present, you must specify
all of these with XML nodes, as described in Table 4-13, "Child
Elements of <wdk:chart> Tag, if ChartData is not used".

chartDataExpr

For more information on the ChartData object, see the Java


API Reference Guide.

No
id

This id is the name of the generated element. It is also used to gen~


erate the names of the two formfields used by the picker: the text
field that displays the name of the instance, and the hidden field that
stores the ID.
If not specified, the value of the widget's name attribute is used.

No
imageMap

If present and set to true, Saba sets the chart to have HTML image
map information. By default, the behavior of an image-mapped chart
is:

When the user hovers the mouse pointer over a chart area, the
browser displays a "tool tip" giving the value of that chart area.
When the user clicks on a chart area, there is no effect.

| WDK Widgets | 73

Child Element

Required

Value
You can change the behavior of clicking on a chart area by using
the imageMapURL child node (described below) or specifying an
image map URL with the ChartData object.

No
type

The type of chart to display. This must be one of the chart types
listed in Table 4-10, "<wdk:chart> Chart Types". Note that this
value is only used if the page is not passed a <chart_id>_type
parameter. If that parameter is passed, the <type> node is ignored.
If the page is not passed a <chart_id>_type parameter and there
is no <type> node, the chart is displayed as a bar25d.

No
wdktags:attachTo

Tag used to attach the field to a Node in the <wdk:model>. This


is described further in "<wdktags:attachTo>""<wdktags:attachTo>"
on page 3-24.

If you do not specify a ChartData object (with the <ChartDataExpr> node), Saba uses the values of the following
child nodes to set the chart data. These nodes are all ignored if there is a ChartData object. Note that some of the
following nodes are listed as "required". They are only required if a ChartData is not specified.
Table 3-20:)Child Elements of <wdk:chart>Tag, ifChartData is not used.
Child Element

imageMapURL

Required

Value

No

The URL to use when generating the imagemap for the chart. If
imageMap i set to true, and imageMapURL is present, Saba
opens this URL when the user clicks anywhere in the chart. The
URL may contain the strings $cat and $ser; Saba replaces these
strings with the name of the category and series for each area of the
chart. Thus, if you set imageMapUrl to

<imageMapURL>javascript:alert(

'Series: $ser ; Category: $cat') </im~


ageMapURL>
Saba sets up appropriate image map URLs for each area of the chart,
replacing $ser and $cat with the appropriate series and category
values for that area of the chart.
If imageMap is set to true and no imageMapURL is specified,
clicking in the chart has no effect. If imageMap is set to false

| WDK Widgets | 74

Child Element

Required

Value
(or not present), imageMapURL is ignored, and clicking in the chart
has no effect.

No

The title of the chart.

No

The name of the X (independent) axis. This is the axis that contains
the category data points. This is usually the horizontal axis, but in
a bar chart this is the vertical axis, and in a pie chart this would be
represented with a legend of the different pie wedges.

No

The name of the Y (dependent) axis. This axis displays the value of
each category data point. This is usually the vertical axis, but in a
bar chart this is the horizontal axis. In a pie chart, the values of the
dependent axis are represented by the size of the various pie wedges.

No

The width of the chart, measured in pixels.

No

The height of the chart, measured in pixels.

Yes

The list of categories, that is, datapoints on the X axis. This node
has several <category> nodes as children, with each <cat~
egory> containing the name of a datapoint. For example, in a chart

title

xLabel

yLabel

width

height

categories

| WDK Widgets | 75

Child Element

Required

Value
showing sales by quarter, the <categories> node might look
like this:

<categories>

<category>2003: Q1</category>

<category>2003: Q2</category>

<category>2003: Q3</category>

<category>2003: Q4</category>

</categories>

Yes
seriesList

The list of data series. This node contains one or more <series>
nodes, each of which represents a single data series. In turn, each
<series> node contains the following child nodes:

<name>: The name of the data series.


<data>: One data node for each chart category, in the same
order.

For example, a chart showing sales by quarter might contain one


data series for each sales region. If the chart contained the <cat~

| WDK Widgets | 76

Child Element

Required

Value
egories> node shown above, it might have the following data
series:

<seriesList>

<series>

<name>Northeast</name>

<data>15000000</data> <!-- Q1 '03-->

<data>17500000</data> <!-- Q2 '03-->

<data>10000000</data> <!-- Q3 '03-->

<data>9000000</data> <!-- ... -->

</series>

<series>

<name>West</name>

<data>25000000</data>

<data>22000000</data>

<data>18000000</data>

<data>21000000</data>

</series>

</seriesList>

| WDK Widgets | 77

Child Element

Required

Value
Note: Pie charts display only the first series listed.

No

If true (the default), show a border around the chart. If false,


do not draw a border.

No

If specified and the chart is a pie type chart then it is used to specify
the label for the "rest" category (that is, the category for values too
small to be shown as individual slices). If not specified, a default
label is printed..

No

If specified and the chart is a pie type chart then it is used to specify
the id for the "rest" category. If not specified, the category is given
the default ID 'rest'

showBorder

restCategoryLabel

restCategoryId

<wdk:chartToolbar>
This widget displays a chart control toolbar. The user can use this toolbar to control what type of chart is used to display
the data, such as changing a bar chart to an area chart.
The toolbar does not link directly to a particular chart. Instead, when the user makes a selection with the chart toolbar,
Saba launches the specified application page, passing the new chart type as a parameter. Any charts on the loaded page
can look for that passed parameter, and use it to set the chart type. For example, if you want the toolbar to control a chart
on the current page, set the toolbar to reload the current page. Any charts on the page could check to see if the toolbar
passed a "chart type" parameter.
The <wdk:chartToolbar> tag has one attribute:
Table 3-20:)Attributes of <wdk:chartToolbar>Tag
Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

It has the following child elements:


Table 3-20:)Child Elements of <wdk:chartToolbar>Tag
Child Element
value

Required

Value

No

The current selection of the toolbar. This must be one of the follow~
ing:

column25d
bar25d

| WDK Widgets | 78

Child Element

Required

Value

pie25d
line
area

Yes

The application page to invoke. If this is a model page (for example,


the current page), the current control page must map it to an appro~
priate control page (as described in "<wdk:links>""<wdk:links>"
on page 3-19).

No

This id is the name of the generated element. It is also used to gen~


erate the names of the two formfields used by the picker: the text
field that displays the name of the instance, and the hidden field that
stores the ID.

href

id

If not specified, the value of the widget's name attribute is used.

No
wdktags:attachTo

Tag used to attach the field to a Node in the <wdk:model>. This


is described further in "<wdktags:attachTo>""<wdktags:attachTo>"
on page 3-24.

Example
This example illustrates how you can use a chartToolbar to control a chart on the same page. Note the following:

When the user makes a selection with the chart toolbar, Saba reloads the current application page.
The chart toolbar's current setting is passed as the out parameter chartType.

| WDK Widgets | 79

The chart examines the value of the chartType parameter to set its type. If the parameter is not passed to the page,
Saba gives it a default value of "column25d".

<?xml version="1.0"?>

<!-- Sample model page using the chartToolbar widget. -->

<xsp:page language="java" xmlns:xsp="http://www.apache.org/1999/XSP/Core"

xmlns:wdktags="http://www.saba.com/XML/WDK/taglib">

<xsp:structure>

<!-- ... -->

</xsp:structure>

<wdk:page xmlns:wdk="http://www.saba.com/XML/WDK">

<!-- the head element contains standard header data for all

WDK model pages -->

<wdk:head>

<wdktags:in>

<!-- The chart type, as passed by the chart toolbar. -->

<wdktags:param name="chartType"

defaultValue="column25d"/>

<!-- ... -->

| WDK Widgets | 80

</wdktags:in>

<wdktags:out>

<!-- Pass the chart toolbar's value as an out

parameter. -->

<wdktags:param name="chartType"/>

</wdktags:out>

<wdk:title>Unit test for chart widget</wdk:title>

</wdk:head>

<wdk:form method="POST">

<wdk:model>

</wdk:model>

</wdk:form>

<wdk:widgets>

<!-- The sample chart. It checks the chartType parameter


chart type.-->

<wdk:chart name="testedWidget">

<id>chart_test</id>

<type><xsp:expr>chartType</xsp:expr></type>

to set the

| WDK Widgets | 81

<!-- ...assume this points to appropriate chart

data... -->

<chartDataExpr>chartDataObject</chartDataExpr>

</wdk:chart>

<!-- This toolbar sets the value of the chartType


<wdk:chartToolbar

name="configuration">

<id>chartType</id> <!-- Name of parameter -->

<!-- The current model page. The control page's

<wdk:links> maps this to the current control

page. -->

<href>chartTest.xml</href>

</wdk:chartToolbar>

</wdk:widgets>

</wdk:page>

</xsp:page>

<wdk:componentPicker>
The <wdk:componentPicker> tag has one attribute:

parameter. -->

| WDK Widgets | 82

Table 3-20:)Attributes of <wdk:componentPicker>Tag


Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:


Table 3-20:)Child Elements of <wdk:componentPicker>Tag
Child Element

Required

Value

No

The name of a JavaScript function to launch when the user clicks


the Done link in the popup window. The function must have the
signature:

callbackFn

function(id, dispName,widgetId)
where widgetId is the ID of this widget. This function is passed
to the picker window as the callback parameter. If callbackFn
is not specified, the popup window is passed the default callback
function setData.
The widget does not examine any return value from the function.

Yes

The name of the component associated with this picker. The user is
able to use the picker to select a particular instance of that compon~
ent.

No

If true, users can type directly in the text box instead of launching
the popup window. Defaults to false.

No

Specifies the height of the picker popup window, in pixels. This


should be a value between 300 and 600. If not specified, defaults to
300.

No

URL of the picker page to use. If not specified, Saba looks up the
reference in the picker page registry.

No

This id is the name of the generated element. It is also used to gen~


erate the names of the two formfields used by the picker: the text
field that displays the name of the instance, and the hidden field that
stores the ID.

component

enable

height

href

id

| WDK Widgets | 83

Child Element

Required

Value
If not specified, the value of the widget's name attribute is used.

No

Variables to submit to the page loaded when this link is clicked.


There may be zero, one, or more input tags. (The format is the
same as for the <field> child element of <wdk:link>.)

Yes

Header text to display in the popup window.

No

Text prompt to show (for example, with a ToolTip) when the


pointer hovers over the link.

No

Size of the text field.

No

Initial value of the (read-only) text field.

No

Initial value of the hidden field. Defaults to the empty string.

No

If "true", the widget is displayed as text, and is not editable. Default


is "false", unless there is a model object (as described in "Widgets
and Model Objects").

No

Specifies the picker's width. Possible values are default (normal


picker width) or wide.

No

Specifies the height of the picker's popup window. Height is specified


in number of pixels. The value should be a number between 300
and 600 (inclusive). Defaults to 300.

No

Tag used to attach the field to a Node in the <wdk:model>. This


is described further in "<wdktags:attachTo>""<wdktags:attachTo>"
on page 3-24.

input

pageHeader

prompt

size

textValue

value

viewOnly

widthType

height

wdktags:attachTo

| WDK Widgets | 84

<wdk:componentWidget>
This widget displays a component. It generates widgets to display each of the component's attributes, custom fields, and
(if appropriate) security domain. It generates IDs for the child widgets this way:
All the component widget does is it generates a set of widgets representing each attribute, the customfields and the
domain (if applicable). The id of each widget is derived from the id of the component widget as follows:

regular attributes: <id of componentWidget>_<nameOfAttribute>


customfields: <id of componentWidget>_customFields
domain: <id of componentWidget>_domain

The <wdk:componentWidget> tag has one attribute:


Table 3-20:)Attributes of<wdk:componentWidget>Tag
Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

It has the following child elements:


Table 3-20:) Child Elements of <wdk:componentWidget>Tag
Child Element

Required

Value

No

Attribute of the component to display. There may be multiple attrib~


ute elemnents. The <attribute> element is described further be~
low.

Yes

Name of the component to display.

No

If true, the component's custome fields are displayed. Defaults to


true.

attribute

componentName

displayCustomFields

| WDK Widgets | 85

Child Element

Required

Value
Note: If a component is not cusotmizable (that is, does not im~
plement the ICustomBusinessView interface), then this flag
must be set to false.

No

Specifies whether the component has a security domain. If not given,


the widget attempts to determine if the component has domain by in~
specting the XML representation of the objcect.

No

This string is used as the root of the HTML names of the generated
input elements. If <id> is not specified, the widget's name attribute
is used instead.

hasDomain

id

Note: The WDK engine creates several HTML form elements,


all of which begin with this ID string. You should regard all names
beginning with this string as "reserved" for the rest of the page.

No
onDomainChange

If the component has a security domain (or if the <hasDomain> flag


is true ), the field is required. This tag is used to specify what action to
perform when the domain picker is used. Since changing domain must
be an atomic operation, the page is submitted when the domain is
changed.
The <onDomainChange> element is described further below.

No

The default value of child widgets' <viewOnly> nodes. By default


this is <false>, unless there is a model object (as described in
"Widgets and Model Objects"). Whatever setting you make here can
be overridden by each <attribute> child's <viewOnly> setting.

Yes

Tag used to attach the field to a Node in the <wdk:model>. This is


described further in "<wdktags:attachTo>""<wdktags:attachTo>" on
page 3-24.

viewOnly

wdktags:attachTo

Note: The customFields widget must be attached to the


<customFields> element generated by a customFieldXM~
LProducer object's visit() method.

No
widgetConfig

Any elements that are inside the widgetConfig node are added to
each of the child widgets.

<attribute> Child Element


Each <attribute> element specifies how one of the component's attributes should be displayed. You may have as
many <attribute> children as you like, up to one for each of the component's attributes.
Note: All attributes are displayed regardless of whether you explicitly provide an <attribute> element. If you
do not provide an element for an attribute, it is displayed with the default settings.

| WDK Widgets | 86

The <attribute> element has the following child elements:


Table 3-20:)Child Elements of<wdk:componentWidget><attribute> Tag
Child Element

Required

Value

Yes

The attribute whose display settings are being set.

No

If true, the attribute is displayed as read only (for example, it might


be displayed in a grayed-out text field).

name

readOnly

Defaults to false. Ignored if <viewOnly> is true.

No
viewOnly

If true, the attribute is displayed with static elements (for example,


text might be displayed as simple HTML text, instead of being in a text
entry component).
The default value is the value of the <wdk:componentWidget>
parent's <viewOnly> node, unless there is a model object (as described
in "Widgets and Model Objects"). If the parent does not have a
<viewOnly> and the page does not have a model object, the default
value is <false>.

isTextArea

No

If true, then string attributes are displayed in a textArea field, instead


of in a single-line text field. By default this is false.

reference

No

If the attribute holds an object ID (that is, it is of type kObjectId),


the reference node holds information about the picker used for that object.
The reference node must have two child elements:

<pickerType>: The type of the picker to use, either


sabaPicker or componentPicker.
<pickerInfo>: Configures the picker. All child elements of this
node are made child elements of the picker widget.

<onDomainChange> Child Element


This element specifies what action the component should take when the security domain changes. The element has the
following child elements:

| WDK Widgets | 87

Table 3-20:) Child Elements of<wdk:componentWidget><onDomainChange>Tag


Child Element

Required

Value

No

The address of a web page to open. Either this element or <mainPage>


must be present.

No

Control file of an application page to open. Either this element or


<href> must be present.

No

Variables to submit to the page loaded when this link is clicked. There
may be zero, one, or more field tags. Syntax is the same as for the
<wdk:link> widget's <field> element.

href

mainPage

field

| WDK Widgets | 88

Example
The following displays a simple component (a domain with a name and description attribute):

<wdk:componentWidget name="componentWidget">

<wdktags:attachTo path="parents/leaf"/>

<componentName>Domain</componentName>

/ <attribute>

<name>name</name>

</attribute>

<attribute>

<name>description</name>

</attribute>

<displayCustomFields>true</displayCustomFields>

<hasDomain>false</hasDomain>

</wdk:componentWidget>
The corresponding view page code segment is:

<xsl:for-each select="parents/leaf/wdk:widget[@name='componentWidget']">

<div>

<xsl:call-template name="ComponentWidgetLayout"/>

</div>

| WDK Widgets | 89

</xsl:for-each>
The template named "ComponentWidgetLayout" is in the stylesheet wdk_widgetlayout.xsl, so the view page
must include that stylesheet:

<xsl:import href="../xsl/view/wdk_widgetlayout.xsl"/>

<!-- Be sure to use correct relative path! -->

<wdk:customFields>
This creates widgets to let the user view and edit a Saba object's custom fields. It works with the
com.saba.web.custom.CustomFieldXMLProducer object. The CustomFieldXMLProducer object
produces an XML representation of a Saba object's custom fields, and the customFields widget knows how to
interpret that XML representation.
The <wdk:customFields> tag has one attribute:
Table 3-20:)Attributes of <wdk:customFields>Tag
Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:


Table 3-20:)Child Elements of<wdk:customFields>Tag
Child Element

Required

Value

Yes

Tag used to attach the field to a Node in the <wdk:model>. This is


described further in "<wdktags:attachTo>""<wdktags:attachTo>" on
page 3-24.

wdktags:attachTo

Note: The customFields widget must be attached to the


<customFields> element generated by a customFieldXM~
LProducer object's visit() method.

No
id

This string is used as the root of the HTML names of the generated input
elements. If <id> is not specified, the widget's name attribute is used
instead.

| WDK Widgets | 90

Child Element

Required

Value
Note: The WDK engine creates several HTML form elements,
all of which begin with this ID string. You should regard all names
beginning with this string as "reserved" for the rest of the page.

No

The title to display above the custom field input widgets. This is a
header for the entire area (not for a specific column), so you should in~
clude at most one <header> tag.

No

The number of columns to use when displaying the custom fields. The
default is 2.

No

If set to true, the values of the custom fields are displayed as plain
(non-editable) text. Default is "false", unless there is a model object
(as described in "Widgets and Model Objects")

No

A number, indicating the default width (in pixels) separating the columns.
By default, this is 20 pixels.

header

columns

viewOnly

columnPadding

| WDK Widgets | 91

Example

<wdk:customFields name="customFields">

<wdktags:atta chTo path="/customFields" root="model"/>

<!-- ...this is the root of the XML node produced by the

CustomFieldXMLProducer.visit method. -->

<id>custom</id>

<columns>2</columns>

<header>Custom Fields</header>

</wdk:customFields>

<wdk:dateInput>
The <wdk:dateInput> widget allows users to enter a date. The displayed widget displays both a text field and a
graphic "calendar" element.

Figure 4-1:) User Interface for <wdk:dateInput> Widget


The user can select a date by clicking the calendar graphic. This brings up a graphical UI letting the user pick a day,
month, and year. When the user has selected a date, it is displayed in the text field in the Saba site's date format (the
format is specified by the administrator). In addition, the widget can optionally permit the user to enter the date text
directly in the text input area. (The widget does not do any checking or processing of the text. If the page designer lets
the user enter text directly, he or she should also use Java code to transform it into a system-appropriate date string or
a Date object.)
The <wdk:dateInput> tag has one attribute:

| WDK Widgets | 92

Table 3-20:)Atributes of <wdk:dateInput> Tag


Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:


Table 3-20:)Child Elements of <wdk:dateInput> Tag
Child Element

Required

Value

No

This id is the name of the generated element. If not specified, the value
of the widget's name attribute is used.

No

Tag used to attach the field to a Node in the <wdk:model>. This is


described further in "<wdktags:attachTo>""<wdktags:attachTo>" on
page 3-24.

No

The label to display next to the element.

No

If true, the widget is displayed in a way indicating that its value is re~
quired (for example, the label might be displayed in red). Defaults to
false.

No

Prompt to show (for example, in a "tool-tip") when the mouse moves


over the calendar icon.

No

If "true", the user can enter a date directly in the text box. (Note that
the widget does not verify that the date is entered in an appropriate
format!)

id

wdktags:attachTo

label

required

prompt

enable

| WDK Widgets | 93

Child Element

Required

Value
If "false", the user must select a date with the calendar widget. The
widget then puts a date string, in the system's default date format, in the
text box.

No

Initial date when the widget is first shown. The widget does not verify
that this text is in an appropriate format.

No

If "true", the widget is displayed as text, and is not editable. Default


is "false", unless there is a model object (as described in "Widgets
and Model Objects").

No

If "true", a colon is appended to the label. (That is, if the label is


"Ship date", it is displayed as "Ship date:".) Default value is
"false".

No

The name of a JavaScript function to launch when the user selects a


date. The function must have the signature:

value

viewOnly

useLabelTerminator

callbackFn

function(dateFieldName,selectedDateString)
The widget does not examine any return value from the function.

No
size

Length of the text field, in characters. By default, this is 15.

| WDK Widgets | 94

The following example illustrates the use of the <wdk:dateInput> widget:

<wdk:dateInput name="abstract">

<wdktags:attachTo path=""/> <!-- A ttach to the <wdk:model> node -->

<value>1/21/2000.</value> <!-- Initial date -->

<required>true</required>

<label>Date</label>

<prompt>Enter a date </prompt> <!-- "tool-tip" if mouse hovers over

the calendar graphic -->

<enable>false</enable> <!-- User cannot enter date in text field -->

</wdk:dateInput>

<wdk:deleteLink>
This widget generates a "delete" link. It behaves exactly like <wdk:link> (described on ), with the following exceptions:

If <label> is not specified, the default Delete label is used.


If <prompt> is not specified, the default Delete prompt is used.
If <icon> is not specified, the default Delete icon is used.
If there is no <field> or <do> child, Saba automatically inserts

<field>

<name>actionKey</name>

<value>delete</value>

</field>

| WDK Widgets | 95

The link causes a confirmation window to be displayed. You can specify a text for this window, or use the default
"confirm delete" text.

The <wdk:deleteLink> widget has all the same attributes and subelements as <wdk:link>. It also has the following
additional subelement:
Table 3-20:)Additional Child Elements of <wdk:deleteLink> Tag
Child Element

Required

Value

No

When the user clicks the link, Saba displays a confirmation window.
If <confirmMesg> is specified, that text is used for the confirm~
ation window. Otherwise, a default "confirm delete" message is
used.

confirmMesg

<wdk:domainPicker>
This is a special picker object used to let the user select what domain he is using. It is like the <wdk:sabaPicker>, except
that it automatically presents the user with a list of domains. In addition, as soon as a domain is changed, the main page
(or another URL) is invoked. The user cannot both change a domain and take other actions on the same page. This is
because of the requirement that domain changes be made atomically.
The widget creates two form elements: a text field for displaying the selection, and a hidden field for storing the ID of
the selection. The name of the hidden field is specified by the text in the widget's <id> child tag. If there is no <id>
tag, the widget's name attribute is used. The name of the text field form element is computed by appending the value
of PickerWidget.kTextFieldExtension to the name of the hidden field.
The <wdk:domainPicker> tag has one attribute:
Table 3-20:)Atributes of <wdk:domainPicker> Tag
Attribute Name

name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:

| WDK Widgets | 96

Table 3-20:)Child Elements of <wdk:domainPicker> Tag


Child Element

callbackFn

Required

Value

No

The name of a JavaScript function to launch when the user clicks


the Done link in the popup window. The function must have the
signature:

function(listName, value, option,


selectedOptionIndex)
where both value and option are strings containing comma-delimited
lists.

| WDK Widgets | 97

Child Element

Required

Value
The widget does not examine any return value from the function.

No

If true, users can type directly in the text box instead of launching
the popup window. Defaults to false.

No

Specifies the height of the picker popup, in pixels. Should be a value


between 300 and 600. Defaults to 300.

No

This id is the name of the generated element. If not specified, the


value of the widget's name attribute is used.

No

The label to display next to the element.

Yes

Specifies the action to perform when a new domain is selected. This


element has required subelements, which are described in "<onPick>
Child Element".

No

Text prompt to show (for example, with a ToolTip) when the


pointer hovers over the link.

No

If true, the widget is displayed in a way indicating that its value


is required (for example, the label might be displayed in red). De~
faults to false.

No

The display name of the domain.

No

If "true", a colon is appended to every label in the list (for example,


if the label is "Taxable", it is displayed as "Taxable:"). Defaults
to "false".

No

The ID of the domain.

No

If "true", the widget is displayed as text, and is not editable. Default


is "false", unless there is a model object (as described in "Widgets

enable

height

id

label

onPick

prompt

required

textValue

useLabelTerminator

value

| WDK Widgets | 98

Child Element

Required

Value
and Model Objects").

viewOnly

No

Specifies the default value of the picker. Acceptable values are


"default" or "wide". If not given, the "default" value is used.

No

Tag used to attach the field to a Node in the <wdk:model>. This


is described further in "<wdktags:attachTo>""<wdktags:attachTo>"
on page 3-24.

widthType

wdktags:attachTo

<onPick> Child Element


This element specifies what action should be taken when the domain is changed. As noted, the action is taken immediately,
as domain changes must be made atomically. The element contains the following child elements:
Table 3-20:)Child Elements of <wdk:domainPicker><onPick> Tag
Child Element

Required

Value

No

If present, specifies the URL to open when the domain is changed.


Either this or <mainPage> must be present.

No

If present, specifies the Saba application page to open when the do~
main is changed. Either this or <href> must be present.

No

Variables to submit to the page loaded when this link is clicked.


There may be zero, one, or more field tags. (The format is the
same as for the <field> child element of <wdk:link>.)

href

mainPage

field

If there are no <field> elements, Saba automatically inserts

<field><name>actionKey</name>
<value>chgDomain</value></field>

<wdk:file>
The <wdk:file> tag is used to define an input field for uploading file content. This tag must be used within a
<wdk:widgets> tag in a WDK page. The file widget allows the page's user to select a local file on his or her device
and upload it to the server.
The <wdk:file> tag has one attribute:

| WDK Widgets | 99

Table 3-20:)Atributes of <wdk:file> Tag


Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:


Table 3-20:)Child Elements of <wdk:file> Tag
Child Element

Required

Value

No

This id is the name of the generated input form element. If not specified,
the value of the name attribute is used.

No

Tag used to attach the field to a Node in the <wdk:model>. This is


described further in "<wdktags:attachTo>""<wdktags:attachTo>" on
page 3-24.

No

The maximum length of the file element.

No

The label to display next to the file element.

No

If true, the widget is displayed in a way indicating that its value is re~
quired (for example, the label might be displayed in red). Defaults to
false.

No

If "true", the widget is displayed as text, and is not editable. Default


is "false", unless there is a model object (as described in "Widgets
and Model Objects").

No

If "true", a colon is appended to the label. (That is, if the label is


"File name", it is displayed as "File name:".) Default value is
"false".

id

wdktags:attachTo

maxlength

label

required

viewOnly

useLabelTerminator

| WDK Widgets | 100

The following example illustrates a file field defined within the <wdk:widgets> section of a WDK page:

<wdk:widgets>

<wdk:file name="theFile">

<id>theFile</id>

<label>the file to upload</label>

</wdk:file>

</wdk:widgets>

<wdk:finderQuery>
Displays the condition fields of a finder, letting the user run searches from this page. The contents of the finder may be
specified with a Java Collection, or through a SQL search.
The <wdk:finderQuery> tag has one attribute:
Table 3-20:)Atributes of <wdk:finderQuery> Tag
Attribute Name

name

Required

Value

Yes

The name of the input field. The view stylesheet uses this name to identify the
widget. For example,

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The <wdk:finderQuery> tag has the following child elements:

| WDK Widgets | 101

Table 3-20:)Child Elements of <wdk:finderQuery> Tag


Child Element

Required

Value

No

This id is the name of the generated input form element. If not


specified, the value of the name attribute is used.

No

Tag used to attach the field to a Node in the <wdk:model>. This


is described further in "<wdktags:attachTo>""<wdktags:at~
tachTo>" on page 3-24.

No

Collection to display. If present, this element must have a child


node of type <expr>, containing a Java expression that evaluates
to type Collection.

id

wdktags:attachTo

collection

Note: The expression may evaluate to any Java Collection.


However, if the Collection is not of type FinderCollec~
tion, the <collection> node is ignored.

No
sqlMessage

A java expression that evaluates to an ISQLMessage. Usually it


is a typesafe enumerated constant.
Note: This should not be specified if there is a <collec~
tion> node. It is required if there is not a <collection>
node.

No
driverData

Driver data for the FinderCollection or SQL query. This must


contain a child <expr> element, containing Java code that eval~
uates to type DriverData.
If not specified, the appropriate default driver data is used.

No
presentationData

The presentation data. This must contain a child <expr> element,


containing Java code that evaluates to type FinderPresenta~
tionData.
If not specified, the default presentation data for the SQL message
is used.

No

Name of the resource bundle to use to evaluate labels.

No

If present, removes a condition. This element must have one and


only one of the following child elements:

resourceBundle

removeCondition

<id>: ID of a fixed condition to remove.

| WDK Widgets | 102

Child Element

Required

Value

<name>: Name of a configurable condition to remove.

No

If present, adds a condition. This element is described further be~


low.

Yes

Describes what to do when the link is clicked. Must have one and
only one of the following child elements:

addCondition

onAction

<href>: URL of web page to open.


<mainPage>: Saba application page to open.

No

Label for the Go button. If not present, default label is used.

No

Label for the Reset button. If not present, default label is used.

No

If true, display a Save Search Query button letting the user


save the search settings, and a Configure link to let the person
configure the search. Defaults to false.

No

Label for the Save Search Query button. If not present, default
label is used. Ignored if <allowSaving> is false or is not
present.

No

Text to display when user saves a query. If not present, default


text is used. Ignored if <allowSaving> is false or is not
present.

No

Label for the Reset Saved Query button. If not present, default
label is used. Ignored if <allowSaving> is false or is not
present.

No

Indicates whether the finder can perform a "blind search", that is,
search with no criteria, thus returning all objects of the relevant
type. Default value is true (finder can perform blind searches).

goLabel

resetLabel

allowSaving

saveQueryLabel

saveQueryDescLabel

resetQueryLabel

allowBlindSearch

| WDK Widgets | 103

Child Element

Required

Value
If set to false, the user must specify at least one search criterion
before running the query.

No

Text to show user if he or she attempts a blind search, and blind


searches are not allowed.

No

Allows you to create a "simple search" version of the finder. If


node is present, the finder displays a Simple link. The behavior
of the link depends on the child of the simpleContext node.
If it contains a <name> child, the page is realoaded, passing that
context name. If the <simpleContext> contains an <href>
child, that child contains the hyperlink to the advanced search
page.

No

Hyperlink to an Advanced Search page. This is used in the simple


version of the search page, and is ignored if SimpleContext is not
present.

No

If true, remove all attributes displayed on the Configure link's


popup window.

allowBlindSearchPrompt

simpleContext

advanceHref

removeAttribute

<addCondition> Child Element


The addCondition element specifies a search condition. This element has the following children:

| WDK Widgets | 104

Table 3-20:)Child Elements of <wdk:finderQuery><childElement> Tag


Child Element

Required

Value

No

ID of a fixed condition to add. Either this or <name> must be present.

No

Name of a configurable condition to add. Either this or <id> must be present.

No

If true, field must contain some value before the field is submitted. Defaults
to false.

No

Label for the condition.

No

Datatype for the condition. Must be one of kString, kInteger, kReal,


kSelect, kBoolean, kDate, kPlainTime, kLov, or kComponent. The
default value is kString.

No

Operator for condition. Must be either kLike or kEquals.

No

Initial value for the condition.

No

Size of the text field.

No

ID of the list of values. Required if <type> is kLov.

No

Required if <type> is kComponent.

No

Required if <type> is kSelect. It must have the following child elements:

id

name

required

label

type

operator

value

size

lovId

component

selectInfo

<className>: Name of the Java Class that generates the drop down menu.
This Java class should implement the com.saba.web.finder.Drop~

| WDK Widgets | 105

Child Element

Required

Value
DownGenerator interface .
Note: The class must have an empty arg constructor. See "Example
3" for an example.

| WDK Widgets | 106

Example 1
In the following example we pass the FinderCollection to both the finderQuery and finderResult widgets.
Note that in the finderQuery widget we also passed presentation data, because the backend finder is an ad hoc finder
without any default driver data or presentation data.

<?xml version="1.0"?>

<xsp:page language="java" xmlns:xsp="http://www.apache.org/1999/XSP/Core"

xmlns:wdktags="http://www.saba.com/XML/WDK/taglib">

<xsp:structure>

<xsp:include>com.saba.finder.*</xsp:include>

<xsp:include>com.saba.locator.*</xsp:include>

<xsp:include>com.saba.msg.*</xsp:include>

</xsp:structure>

<!-- the root user element.


resulting from the search

In this case, the result lists the domains

-->

<wdk:page xmlns:wdk="http://www.saba.com/XML/WDK">

<!-- the head element contains standard header data for all WDK model pages
-->

<wdk:head>

</wdk:head>

<wdk:form method="POST">

| WDK Widgets | 107

<xsp:logic>

<!-- an adhoc finder. -->

ISQLMessage sqlMessage = PlatformFinder.kGetSecurityMembers_id;

<!-- since this finder does not have default driver data or presentation
data associated with it

we create DriverData and FinderPresentationData objects explcitly.

-->

DriverData driverData = new DriverData();

<!--

This ad hoc finder has one fixed condition.

-->

driverData

.addCondition(1,"listl000000000001000",com.saba.permeta.RegularAttributeDatatype.kString);

<!--

We need to specify how this condition is to be presented.

-->

FinderPresentationData presentationData = new FinderPresentationData();

| WDK Widgets | 108

<!-- construct item presentation data for condition 1 -->

ItemPresentationData condition1 = new ItemPresentationData(1);

<!-- whether or not we want the condition to be required

-->

condition1.setRequired(false);

<!--

The label we want to paint. Should be i18n!

-->

condition1.setLabel("List ID");

presentationData.addPresentationData(condition1);

<!-- now let's get the FinderCollection. Note we only assume that a
Collection is returned.

It is the finder widget that checks if the Collection passed in is a


subclass of

FinderCollection and behaves appropriately

-->

FinderManager manager =

(FinderManager)getServiceLocator().getManager(Delegates.kFinder);

Collection collection = manager.find(sqlMessage,driverData);

| WDK Widgets | 109

</xsp:logic>

<wdk:model>

</wdk:model>

</wdk:form>

<wdk:widgets>

<wdk:finderQuery name="testedWidget">

<id>fqw</id>

<collection><expr>collection</expr></collection>

<presentationData><expr>presentationData</expr></presentationData>

<onAction>

<href>finders.xml</href>

</onAction>

</wdk:finderQuery>

</wdk:widgets>

</wdk:page>
</xsp:page>

| WDK Widgets | 110

Example 2
This example uses a fully instrumented finder (meta-finder) that has full default information for driver data as well as
presentation data.

<xsp:page language="java" xmlns:xsp="http://www.apache.org/1999/XSP/Core"

xmlns:wdktags="http://www.saba.com/XML/WDK/taglib">

<xsp:structure>

<xsp:include>com.saba.finder.*</xsp:include>

<xsp:include>com.saba.locator.*</xsp:include>

<xsp:include>com.saba.msg.*</xsp:include>

</xsp:structure>

<!-- the root user element.


resulting from the search

In this case, the result lists the domains

-->

<!-- the head element contains standard header data for all WDK model pages
-->

<wdk:head>

</wdk:head>

<wdk:form method="POST">

<xsp:logic>

| WDK Widgets | 111

ISQLMessage sqlMessage = PlatformFinder.kPortletMetaFinder;

FinderManager manager =

(FinderManager)getServiceLocator().getManager(Delegates.kFinder);

Collection collection = manager.find(sqlMessage);

</xsp:logic>

<!-- invoke the action -->

<wdk:model>

<configuration/>

</wdk:model>

</wdk:form>

<wdk:widgets>

<!-- . . . -->

<wdk:finderQuery name="testedWidget">

<wdktags:attachTo path="configuration"/>

<id>portalQuery</id>

<collection><expr>collection</expr></collection>

<onAction>

| WDK Widgets | 112

<href>finders2.xml</href>

</onAction>

</wdk:finderQuery>

</wdk:widgets>

</wdk:page>

</xsp:page>
Example 3
This example illustrates how to generate a drop down menu in the Finder Query widget.

<wdk:finderQuery name="testedWidget">

<wdktags:attachTo path="configuration"/>

<id>portalQuery</id>

<collection>

<expr>collection</expr>

</collection>

<onAction>

<href>finders3.xml</href>

</onAction>

<addCondition>

| WDK Widgets | 113

<id>10</id>

<label>LOV test</label>

<type>kLov</type>

<lovId>listi000000000000905</lovId>

</addCondition>

<addCondition>

<id>11</id>

<label>AdHoc Drop Down Test</label>

<type>kSelect</type>

<required>true</required>

<selectInfo>

<className>com.saba.web.finder.SampleDropDownGenerator</className>

<contextName>testing</contextName>

</selectInfo>

</addCondition>

</wdk:finderQuery>
The finder uses a SampleDropDownGenerator object to generate the menu. That object has the following code:

| WDK Widgets | 114

/**

* Sample to Generate the Collection of Drop down Values

*/

public class SampleDropDownGenerator implements DropDownGenerator

/**

* Empty Constructor

*/

public SampleDropDownGenerator()

/**

* Get the Collection of List

* @param locator

* @param contextName name of the context

*/

public Collection getOptionValues(ServiceLocator locator, String contextName)

| WDK Widgets | 115

throws

SabaException

List tempList = new ArrayList();

for(int i=0; i < 10; i++)

List temp = new ArrayList();

temp.add("Testing" + i);

temp.add("Testing" + i);

tempList.add(temp);

return tempList;

<wdk:finderResult>
Displays the result of a finder query.
The <wdk:finderResult> tag has one attribute:

| WDK Widgets | 116

Table 3-20:)Atributes of <wdk:finderResult> Tag


Attribute Name

Required

Value

Yes

The name of the input field. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The <wdk:finderResult> tag has the following child elements:


Table 3-21:)Child Elements of <wdk:finderResult> Tag
Child Element

Required

Value

No

This id is the name of the generated input form element. If not specified,
the value of the name attribute is used.

No

Tag used to attach the field to a Node in the <wdk:model>. This is


described further in "<wdktags:attachTo>""<wdktags:attachTo>" on
page 3-24.

No

ID of the finderQuery widget whose results are being displayed.


Either this or <driverData> must be present.

No

Collection to display. If present, this element must have a child node


of type <expr>, containing a Java expression that evaluates to type
Collection.

id

wdktags:attachTo

queryWidgetId

collection

Note: If collection is not an instance of com.saba.finder.Finder~


Collection, then you must supply a FinderResultCustomizer that
implements the paint(Document,Iterator,int) method.
If this collection is an instance of FinderCollection, then
<sqlMessage> and <driverData> are optional.

No
sqlMessage

A java expression that evaluates to an ISQLMessage. Usually it is a


typesafe enumerated constant.
Note: This need not be specified if there is a <collection>
node that returns a FinderCollection. Otherwise it is re~
quired. It is required if there is not a <collection> node.

No
driverData

Driver data for the FinderCollection or SQL query. This must contain
a child <expr> element, containing Java code that evaluates to type
DriverData.
If not specified, the appropriate default driver data is used.

| WDK Widgets | 117

Child Element

Required

Value
Note: This need not be specified if there is a <collection>
node that returns a FinderCollection.

No
presentationData

The presentation data. This must contain a child <expr> element,


containing Java code that evaluates to type FinderPresenta~
tionData.
If not specified, the default presentation data for the SQL message is
used.

No

Customizer to use. If present, must have an <expr> child containing


a Java expression that evaluates to type com.saba.web.find~
er.FinderResultCustomizer.

No

If present, overrides the content of the display column. There may be


zero, one, or more <content> tags. This tag is described further be~
low.

No

Contains columns to display to the left of the table. Must contain one
<column> tag for each column to display. The <column> tag may
contain widgets, XML data, or <subelement> nodes (described
below).

No

Contains columns to display to the right of the table. Must contain one
<column> tag for each column to display. The <column> tag may
contain widgets, XML data, or <subelement> nodes (described
below).

No

Name of the resource bundle to use to evaluate labels.

No

Removes a column from the results table. This element must have one
and only one of the following child elements:

customizer

content

leftWidgets

rightWidgets

resourceBundle

removeDisplayColumn

No
addDisplayColumn

<id>: ID of a fixed column to remove.


<name>: Name of a configurable column to remove.

Adds a column to the results table. This element must have one and
only one of the following child elements:

<id>: ID of a fixed column to add.


<name>: Name of a configurable column to add.

It must also have a <label> child, specifying the column header. It


may optionally have a <size> child, specifying the width of the

| WDK Widgets | 118

Child Element

Required

Value
column either in pixels (<size>40</size>) or as a percentage
(<size>25%</size>).

No

If true, the FinderIterator is recomputed. Defaults to false.

No

If set to true, the page displays all records returned by the search,
instead of breaking the results up into pages. Default is false.

No

Number of rows to display on each page. (Ignored if showAll is


true.)

No

Text that displays how many records were found.

No

Text to display if no records were found.

No

If present, removes a condition. This element must have one and only
one of the following child elements:

refresh

showAll

pageSize

statusLabel

noRecordsLabel

removeCondition

<id>: ID of a fixed condition to remove.


<name>: Name of a configurable condition to remove.

No

If present, adds a condition. This element is described further below.

Yes

Describes what to do when the Next or Prev link is clicked. Must have
one and only one of the following child elements:

addCondition

onAction

<href>: URL of web page to open.

| WDK Widgets | 119

Child Element

Required

Value

<mainPage>: Saba application page to open.

No

Label for the Next button. Node must have a single <label> node,
containing the label text.

No

Label for the Prev button. Node must have a single <label> node,
containing the label text.

No

If true, provide an Print link to open the data in a browser window,


formatted suitably for printing. If false, do not provide this link.
Defaults to true.

No

If true, provide an Export link to let users export table to CSV format.
If false, do not provide this link. Defaults to true.

No

Specified that a column can be used to sort the table. This node must
contain the column's ID (for a fixed column) or name (for a configurable
column). You may have as many or as few <sort> elements as you
wish.

No

If present, creates an Add button letting users add objects of the type
shown by the finder, and specifies what page should be launched by
the Add button. For details about the onAdd widget's contents, see
below.

No

If true, creates a Modify Table link that the user can click to modify
the table view. Defaults to false.

No

Label for the Modify Table button. If not present, default label is used.

No

Set to true to indicate page position should be reset to beginning.


Default is false.

No

If true, remove all attributes displayed on the Configure link's popup


window.

next

prev

print

export

sort

onAdd

modifyTable

modifyTableLabel

resetPosition

removeAttribute

| WDK Widgets | 120

<subelement> Child Element


The <leftWidgets> and <rightWidgets> elements, if present, each contain one or more <column> tags. The
<column> tags can contain widgets or XML data. They can also contain <subelement> nodes. The <subelement>
has the following children:
Table 3-20:)Child Elements of <wdk:finderResult><...Widgets><column><subelement> Tag
Child Element

Required

Value

Yes

Contains the data to display in this column. May have a @type attrib~
ute, described below.

No

Header of this column.

data

label

The data element can have the following attributes:


Table 3-20:)Atributes of <wdk:finderResult><...Widgets><column><subelement><data> Tag
Child Element

type

Required

Value

No

Type of data to display in the column. This affects how the column is
formatted. Possible values are:

"icon": Column contains an image. This column is not printed


or exported.
"checkbox": Column contains a checkbox. The value exported
or printed is Y for a checked box and N for an unchecked box.
"radio": Column contains radio buttons. The value exported or
printed is the name of the selected radio button. If no button is se~
lected, the empty string is exported/printed.
"select": ": The column contains dropdowns menus. The value
exported/printed is the label for the selected value. If no value is
selected, the value of the first item in the dropdown is exported/prin~
ted.
"text": The column contains text or a text link.
If the type is not specified, the default is "text".

If the data type is checkbox, the <data> node has the following child elements:

| WDK Widgets | 121

Table 3-20:)Child Elements of <wdk:finderResult><...Widgets><column><subelement><data> Tag


Child Element

Required

Value

No

If true, display a Check All box in the column header. Users can
click that box to check or uncheck all columns. Default is false.

Yes

Checkbox name to use when submitting. This must contain a <pre~


fix> node specifying the prefix of the node's name. It may optionally
contain a <postfix> specifying a postfix for the ID.

Yes

Checkbox value to use when submitting.

checkAll

id

value

<onAdd> child element


If the <onAdd> child element is present, the results screen has an Add button. The element specifies which page is
opened when the user clicks Add. This node follows the syntax of <wdk:link> or <wdk:frameworkLink>. In addition,
it can have the following child elements:
Table 3-20:)Child Elements of <wdk:finderResult><onAdd> Tag
Child Element

Required

Value

No

If specified (and no label tag is specified) then the display name of the
component is used to autogenerate the label of the link.

No

Indicates what type of action is used to create the object. This is used
in combination with component to generate the label for the button,
if ?the onAdd does not have label or prompt tags. Appropriate
values are create (default) or add. If a label is not specified, the
application generates an appropriate label from this and the component's
display name (for example, "Add Course", "Create Goal").

component

actionType

| WDK Widgets | 122

Example 1
In this example the finder result widget displays the column explicitly specified in the widget declaration:

<wdk:finderResult name="result">

<export>false</export>

<print>false</print>

<queryWidgetId>query</queryWidgetId>

<sqlMessage>PlatformFinder.kGeneratorTestObjectMetaFinder</sqlMessage>

<driverData>

<expr>driverData</expr>

</driverData>

<presentationData>

<expr>presentationData</expr>

</presentationData>

<addDisplayColumn>

<name>name</name>

<label>

<wdktags:i18n.componentAttribute component="GeneratorTestObject"
attribute="name"/>

</label>

| WDK Widgets | 123

<size>80%</size>

</addDisplayColumn>

<onAction>

<href>pickGeneratorTestObject.xml</href>

</onAction>

</wdk:finderResult>
Note that the <queryWidgetId> tag is used to identify the source of the driver data values coming from a query
widget. (In the example above the <driverData> and <presentationData> are used to make sure the default
values are not used.)

| WDK Widgets | 124

Example 2
In this example a left widget column is added to the result:

<wdk:finderResult name="result">

<queryWidgetId>query</queryWidgetId>

<sqlMessage>PlatformFinder.kGeneratorTestObjectMetaFinder</sqlMessage>

<driverData>

<expr>driverData</expr>

</driverData>

<presentationData>

<expr>presentationData</expr>

</presentationData>

<leftWidgets>

<column>

<data type="icon">

<wdk:link name="pick">

<icon>

<wdktags:i18n.path bundle="common" name="kI18nXXXXiconPlus"/>

</icon>

| WDK Widgets | 125

<action>

<type>process</type>

<function>

<name>window.opener.<xsp:expr>callback</xsp:expr>

</name>

<arg type="String">

<wdk:display>

<id>0</id>

</wdk:display>

</arg>

<arg type="String">

<wdk:display>

<name>name</name>

</arg>

<arg type="String">

<xsp:expr>formfield</xsp:expr>

</arg>

| WDK Widgets | 126

</function>

</action>

<action>

<type>close</type>

</action>

</wdk:link>

</data>

</column>

</leftWidgets>

<addDisplayColumn>

<name>name</name>

<label>

<wdktags:i18n.componentAttribute component="GeneratorTestObject"
attribute="name"/>

</label>

<size>80%</size>

</addDisplayColumn>

<onAction>

| WDK Widgets | 127

<href>pickGeneratorTestObject.xml</href>

</onAction>
Example 3
In this example the content of the table is overridden. Instead of text, a link is displayed.

<wdk:finderResult name="result">

< quer yWidgetId>query</queryWidgetId>

<sqlMessage>PlatformFinder.kGeneratorTestObjectMetaFinder</sqlMessage>

<driverData>

<expr>driverData</expr>

</driverData>

<presentationData>

<expr>presentationData</expr>

</presentationData>

<leftWidgets>

<column>

<data type="icon">

<wdk:link name="pick">

<icon>

| WDK Widgets | 128

<wdktags:i18n.path bundle="common"

name="kI18nXXXXiconPlus"/>

</icon>

<action>

<type>process</type>

<function>

<name>window.opener.<xsp:expr>callback</xsp:expr></name>

<arg type="String">

<wdk:display>

<id>0</id>

</wdk:display>

</arg>

<arg type="String">

<wdk:display>

<name>name</name>

</wdk:display>

</arg>

| WDK Widgets | 129

<arg type="String">

<xsp:expr>formfield</xsp:expr>

</arg>

</function>

</action>

<action>

<type>close</type>

</action>

</wdk:link>

</data>

</column>

</leftWidgets>

<addDisplayColumn>

<name>name</name>

<label>

<wdktags:i18n.componentAttribute

component="GeneratorTestObject" attribute="name"/>

</label>

| WDK Widgets | 130

<size>80%</size> </addDisplayColumn> <content> <name>name</name> <data>


<wdk:link name="view">
<label>
<wdk:display>
<name>name</name>
</wdk:display>
</label>
<action>
<href>editGeneratorTestObject.xml</href>
</action>
</wdk:link> </data>

| WDK Widgets | 131

</content> <onAction> <href>pickGeneratorTestObject.xml</href>


</onAction></wdk:finderResult>
Example 4
The following example set data type to be "checkbox".

<wdk:finderResult name="result">

<id>por talRe sult</id>

<queryWidgetId>portalQuery</queryWidgetId>

<sqlMessage>PlatformFinder.kPortalMetaFinder</sqlMessage>

<removeDisplayColumn>

<name>pageManagerClass</name>

</removeDisplayColumn>

<leftWidgets>

<column >

<data type="checkbox">

<checkAll>true</checkAll>

<id>checkBoxId</id>

<value>

<wdk:display>

<id>0</id>

| WDK Widgets | 132

</wdk:display>

</value>

</data>

</column>

</leftWidgets>

<rightWidgets>

<column>

<data type="icon">

<wdk:frameworkLink name="delete">

<icon><wdktags:i18n.path name="kI18nXXXXDeleteGif1"
bundle="common"/></icon>

<action>

<type>process</type>

<function>

<name>confirm</name>

<arg type="String">

<wdktags:i18n.label bundle="common"

| WDK Widgets | 133

name="kI18nXXXXDeleteConfirmation"/>

</arg>

</function>

</action>

<mainPage>findPortal.xml</mainPage>

<field>

<name>actionKey</name>

<value>delete</value>

</field>

<field>

<name>id</name>

<value>

<id>0</id>

</wdk:display>

</value>

</field>

<prompt><wdktags:i18n.label bundle="common"

| WDK Widgets | 134

name="kI18n5006DeletePrompt"/></prompt>
</wdk:frameworkLink> </data> </column> </rightWidgets> <xsp:logic> if (actionKey.equals("delete")) {
<refresh>true</refresh> } </xsp:logic> <onAction> <mainPage>findPortal.xml</mainPage>
</onAction></wdk:finderResult>
All checkboxes under first column have same name, "checkBoxId", and the value submitted with each checkbox is the
id column of the row. To use the value submitted by the checkbox, use the following code:

<wdktags:in> <wdktags:param name="checkBoxId" re


type="String[]"/></wdktags:in>
The variable checkBoxId contains an array of selected ids.

quired="false"

| WDK Widgets | 135

Example 5
Adds sort columns by specifying the <sort> tag.

<wdk:finderResult name="result">

<queryWidgetId>query</queryWidgetId>

<sqlMessage>PlatformFinder.kPortletMetaFinder</sqlMessage>
<removeDisplayColumn>

<name>pageManagerClass</name>

</removeDisplayColumn>

<leftWidgets>

<column>

<data type="icon">

<wdk:frameworkLink name="edit">

<icon><wdktags:i18n.path name="kI18nXXXXiconEdit" bundle="common"/></icon>

<mainPage>editPortlet.xml</mainPage>

<field>

<name>actionKey</name>

<value>edit</value>

</field>

| WDK Widgets | 136

<field>

<name>id</name>

<value>

<wdk:display>

<id>0</id>

</wdk:display>

</value>

</field>

<prompt><wdktags:i18n.label bundle="common"

name="kI18n5006EditPrompt"/></prompt>

</wdk:frameworkLink>

</data>

</column>

</leftWidgets>

<rightWidgets>

<column>

<data type="icon">

| WDK Widgets | 137

<wdk:frameworkLink name="delete">

<icon><wdktags:i18n.path name="kI18nXXXXDeleteGif1"
bundle="common"/></icon>

<action>

<type>process</type>

<function>

<name>confirm</name>

<arg type="String">

<wdktags:i18n.label bundle="common" name="kI18nXXXXDeleteConfirmation"/>

</arg>

</function>

</action>

<mainPage>findPortlet.xml</mainPage>

<field>

<name>actionKey</name>

<value>delete</value>

</field>

<field>

| WDK Widgets | 138

<name>id</name>

<value>

<wdk:display>

<id>0</id>

</wdk:display>

</value>

</field>

<prompt><wdktags:i18n.label bundle="common"

name="kI18n5006DeletePrompt"/></prompt>

</wdk:frameworkLink>

</data>

</column>

</rightWidgets>

<xsp:logic>

if (actionKey.equals("delete")) {

<refresh>true</refresh>

| WDK Widgets | 139

</xsp:logic>

<onAction>

<mainPage>findPortlet.xml</mainPage>

</onAction>

<sort>name</sort>

<sort>type</sort>

</wdk:finderResult>
Example 6
Using FinderCollections: Fully instrumented metadata finder

<xsp:page language="java" xmlns:xsp="http://www.apache.org/1999/XSP/Core"

xmlns:wdktags="http://www.saba.com/XML/WDK/taglib">

<xsp:structure>

<xsp:include>com.saba.finder.*</xsp:include>

<xsp:include>com.saba.locator.*</xsp:include>

<xsp:include>com.saba.msg.*</xsp:include>

</xsp:structure>

<!-- the root user element.


resulting from the search

-->

In this case, the result lists the domains

| WDK Widgets | 140

<wdk:page xmlns:wdk="http://www.saba.com/XML/WDK">

<!-- the head element contains standard header data for all WDK model pages
-->

<wdk:head>

</wdk:head>

<wdk:form method="POST">
<xsp:logic>

ISQLMessage sqlMessage = PlatformFinder.kPortletMetaFinder;

FinderManager manager =

(FinderManager)getServiceLocator().getManager(Delegates.kFinder);

Collection collection = manager.find(sqlMessage);

</xsp:logic>

<wdk:model>

</wdk:model>

</wdk:form>

<wdk:widgets>

<wdk:finderQuery name="testedWidget">

| WDK Widgets | 141

<wdktags:attachTo path="configuration"/>

<id>portalQuery</id>

<collection><expr>collection</expr></collection>

<onAction>

<href>finders2.xml</href>

</onAction>

</wdk:finderQuery>

<!-- input fields for name & description.-->

<wdk:finderResult name="testedWidget">

<wdktags:attachTo path="configuration"/>

<id>portalResult</id>

<queryWidgetId>portalQuery</queryWidgetId>

<collection><expr>collection</expr></collection>

<onAction>

<href>finders2.xml</href>

</onAction>

</wdk:finderResult>

| WDK Widgets | 142

<wdk:parameters name="parameters">

<wdktags:attachTo path=""/>
e </wdk:widgets>

</wdk:page>

</xsp:page>
Example 7
Adds label to the leftWidget/rightWidget nodes.

<wdk:finderResult name=

"testedWidget">

<leftWidgets>

<column>

<label>Testing Lable</label>

<data width="20" type="icon">

<wdk:link>

<icon>

<wdktags:i18n.path

name="kI18nIconCalendarImg" bundle="common_images"/>

</icon>

</wdk:link>

| WDK Widgets | 143

</data>

</column>

</leftWidgets>

<rightWidgets>

<column>

<label>Testing Lable333</label>

<data width="50" type="icon">

<wdk:link>

<icon>

<wdktags:i18n.path

name="kI18nIconDeleteImg" bundle="common_images"/>

</icon>

</wdk:link>

<wdk:link>

<icon>

<wdktags:i18n.path

name="kI18nIconEditImg" bundle="common_images"/>

| WDK Widgets | 144

</icon>

</wdk:link>

</data>

</column>

</rightWidgets>

</wdk:finderResult>

<wdk:finderControl>
Displays the controls (Previous and Next buttons) for a finder iterator. This widget has the following attributes:
Table 3-20:)Atributes of <wdk:finderControl> Tag
Attribute Name

name

Required

Value

Yes

The name of the input field. The view stylesheet uses this name to identify the
widget. For example,

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The element has the following children:

| WDK Widgets | 145

Table 3-20:)Child Elements of <wdk:finderControl> Tag


Child Element

Required

Value

No

This id is the name of the generated input form element. If not specified,
the value of the name attribute is used.

No

Tag used to attach the field to a Node in the <wdk:model>. This is


described further in "<wdktags:attachTo>""<wdktags:attachTo>" on
page 3-24.

yes

Java expression of type FinderIterator.

Yes

Java expression of type FinderControl.

No

Number of rows to display on each page.

No

Text to display how many records were found. If not present, default
text is used.

No

Text to display if no records were found. If not present, default text is


used.

Yes

Describes what to do when the link is clicked. Must have one and only
one of the following child elements:

id

wdktags:attachTo

finderIteratorExpr

finderControlExpr

pageSize

statusLabel

noRecordsLabel

onAction

No

Label for the Next button. Node must have a single <label> node,
containing the label text.

No

Label for the Prev button. Node must have a single <label> node,
containing the label text.

<next>

<prev>

<href>: URL of web page to open.


<mainPage>: Saba application page to open.

| WDK Widgets | 146

<wdk:form>
The <wdk:form> tag is used to define the main WDK form in a WDK page. WDK Forms are the standard widget
used to submit information from a page back to the server. Each Form has a corresponding JavaScript function for
submission of the form and may have multiple hidden fields.
Note: There must be only one <wdk:form> definition per page.
A WDK Form may have the following child elements only:

hidden fields that the form requires


Note: Hidden fields can be used to pass a static value to the next page, as described in "<wdk:hiddenField>".
However, you should usually pass values by declaring an output parameter in the <wdktags:out> section
(as described in "<wdktags:out>""<wdktags:out>" on page 3-35).

<wdk:model> element (described in "<wdk:model> and <wdk:view>""<wdk:model> and <wdk:view>" on page


3-19)
<xsp:logic> element(s) for any necessary setup logic, such as creating beans

When processed, this tag generates an HTML form with a JavaScript function to submit the form to the server, for
example:

<script language="JavaScript">

function wdkSubmitForm(url) {

document.theForm.action=url;

document.theForm.submit();

</script>
This <wdk:form> tag has the following attributes:
Table 3-20:)Atributes of <wdk:form> Tag
Attribute Name

Required

Value

No

Specifies whether the form uses HTTP "POST" or "GET". The default is
"GET".

No

Specifies the MIME type for the form's submission. This must be either:

method

enctype

"application/x-www-form-urlencoded" (the default)

| WDK Widgets | 147

Attribute Name

Required

Value
or
"multipart/form-data" (for uploading files)

<wdk:frameworkLink>
This special-purpose widget creates a link to the start page for the desktop. In addition, it passes the following field to
that page:

clientMainPresentation: The currently active value (present in the request object at run time) is passed.

If no child elements are specfied, then this link generates a field parameter as if the developer had typed the following
child element into a particular link specification on a model page:

<field>

<name>clientMainPresentation</name>

<value>currentlyActiveValue</value>

</field>
The value element above use the currently active values present in the request object at run time.
The widget tag can also explicitly specify the value to pass as clientMainPresentation, by declaring a <field>
child of the <wdk:frameworkLink> widget.
This widget's attributes, and child tags, are exactly like those of <wdk:link> (described), with the following exception:

Any <action> tags that attempt to launch a new page-such as submit or sabapage actions-is ignored. Instead,
after the last <action> is run, the start page is launched.

The <wdk:frameworkLink> widget provides one child tag in addition to those defined by <wdk:link>:
Table 3-20:)Child Elements of <wdk:frameworkLink> Tag
Child Element
mainPage

Required

Value

No

If provided, this attribute specifies the URL of the main page for this
site. If not provided, the widget uses the value returned by re~
quest.getParameter( "clientMainPresentation").

| WDK Widgets | 148

The simple examples below show how the widget can be used. There are many additional attributes and elements that
are not shown here. Please see the <wdk:link> section (on page ) for additional information.

<wdk:frameworkLink name = "noFields">

<label>No Fields</label>

</wdk:frameworkLink>

<wdk:frameworkLink name = "oneField">

<field>

<name>Age</name>

<value>75</value>

</field>

<label>One Field</label>

</wdk:frameworkLink>

<wdk:frameworkLink name = "mainPageSpecified">

<mainPage>/sysadmin/security/domain/domainMain.saba</mainPage>

<label>Main page</label>

</wdk:frameworkLink>

<wdk:frameworkLink name = "mainPlus">

<mainPage>/sysadmin/security/domain/domainMain.saba</mainPage>

| WDK Widgets | 149

<label>Main, Nav and another field</label>

<field>

<name>Age</name>
<value>75</value>

</field>

</wdk:frameworkLink>

<wdk:functionality>
This widget enables you to mark certain areas of a page as being connected with an area of Saba functionality. If the
functionality is turned on (through the Saba Functionality On/Off screen), certain widgets are executed. If it is turned
off, other widgets are executed.
The <wdk:functionality> tag has one attribute:
Table 3-20:)Atributes of <wdk:functionality> Tag
Attribute Name

Required

Value

Yes

The name of the functionality whose status is being checked.

name

The widget has the following child elements:


Table 3-20:)Child Elements of <wdk:functionality> Tag
Child Element

Required

Value

No

Contains one or more widgets. These widgets are executed if the func~
tionality specified by "name" is turned on.

No

Contains one or more widgets. These widgets are executed if the func~
tionality specified by "name" is turned off.

on

off

Note: You can nest one functionality area inside another. However, these must be for two different functional
areas. For example, you might nest a "learning" functionality widget inside a "planning" functionality. If you try
to nest a "learning" functionality widget inside another "learning" functionality, it causes a page compilation error.

| WDK Widgets | 150

Example
This code displays different text depending on whether or not the "Planning" functionality is enabled:

<wdk:functionality name="Functionality.Plan">

<on>

<wdk:pageText name="testWidget">

<resource>kPlanFunctionalityOn</resource>

</wdk:pageText>

</on>

<off>

<wdk:pageText name="testWidget">

<resource>kPlanFunctionalityOff</resource>

</wdk:pageText>

</off>

</wdk:functionality>

<wdk:genericText>
The <wdk:genericText> widget creates a text entry field. Depending on the maximum size and threshold values
specified, Saba creates either a single-line input box or a multi-line text area.
The <wdk:genericText> tag has one attribute:

| WDK Widgets | 151

Table 4-47:)Attributes of <wdk:genericText> Tag


Attribute Name

name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:

| WDK Widgets | 152

Table 4-48:)Child Elements of <wdk:genericText> Tag


Child Element

Required

Value

No

Height of the widget in lines. This is ignored if the widget is displayed


as a sigle-line textbox.

No

This id is the name of the generated element. If not specified, the value
of the widget's name attribute is used.

No

The label to display next to the element.

No

Maximum length of the text field. If this value is greater than


threshold, the widget is displayed as a multi-line text input box.

No

If true, the widget is displayed in a way indicating that its value is re~
quired (for example, the label might be displayed in red). Defaults to
false.

No

If the maxLength attribute is greater than this number, the widget is


displayed as a multi-line text input field. Otherwise, it is displayed as a
single-line input box. If not specified, the default value is 32.

No

If "true", a colon is appended to the label. (That is, if the label is


"Notes", it is displayed as "Notes:".) Default value is "false".

No

If "true", the widget is displayed as text, and is not editable. Default


is "false", unless there is a model object (as described in "Widgets
and Model Objects").

No

Tag used to attach the field to a Node in the <wdk:model>. This is


described further in "<wdktags:attachTo>""<wdktags:attachTo>" on
page 3-24.

No

The width of the widget in characters. If the widget is displayed as a


single-line textbox, this is the size of the widget.

height

id

label

maxLength

required

threshold

useLabelTerminator

viewOnly

wdktags:attachTo

width

| WDK Widgets | 153

<wdk:hiddenField>
The <wdk:hiddenField> tag is used to define hidden fields within a WDK form. For each hidden field defined
within the form, a JavaScript function is generated that sets the value of the field in the main form.

<script language="JavaScript">

function setHiddenField<xsl:value-of select="name"/>(value)

document.WDK_Main_Form.<xsl:value-of

select="name"/>.value=value;

</script>
Note: Usually, you should define output parameters by using the <wdktags:out> section, as described in
"Declaring In/Out Parameters""Declaring In/Out Parameters" on page 3-11. However, in some special circumstances
you may find it useful to declare a hidden field.
The <wdk:hiddenField> tag has the following child elements:
Table 4-49:)Child Elements of <wdk:hiddenField> Tag
Attribute Name

Required

Value

Yes

The name of the hidden field.

No

Initial value of the field.

name

value

| WDK Widgets | 154

The following code fragment demonstrates the complete definition of a <wdk:form> element in the page
view_domain.xml. In this example, the WDK form contains one hidden field and a WDK model that is constructed
using the <wdktags:execute> tag.

<wdk:form method="GET">

<wdk:hiddenField>

<name>do

mainID</name>

<value><xsp:expr>domainId</xsp:expr></value>

</wdk:hiddenField>

<wdk:model>

<!-- detail information for this domain -->

<!-- call a command to serialize the domain name and

<wdktags:execute

manager="com.Saba.client.domain.DomainCommandManager"

command="getDomainAndParents" argument="domainId"/>

</wdk:model>

</wdk:form>

<wdk:impresence>
The <wdk:impresence> widget is used to display information about a user's Instant Messenger accounts. The widget
is displayed as an icon and a clickable link. If the user clicks the link, the widget opens a popup window showing
information about a target user's instant messenger accounts. If the target user and the source user both have accounts

| WDK Widgets | 155

on the same service, the widget displays the target user's account on that service as a clickable hyperlink. The user can
click the link to open an IM chat window with the target user.
The <wdk:impresence> tag has one attribute:
Table 4-50:)Attributes of <wdk:impresence> Tag
Attribute Name

Required

Value

Yes

The name of the input field. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:


Table 4-51:)Child Elements of <wdk:impresence> Tag
Child Element

Required

Value

Yes

Saba ID of the user whose instant messenger information is being dis~


played.

No

Saba ID of the source user. (This is used to determine which of the tar~
get's instant-messenger accounts should be displayed as hyperlinks.)
Default value is the currently logged-in user.

No

Icon to click to open the pop-up window showing the instant messenger
information. If not specified, a default icon is used.

No

Prompt to display next to icon. Clicking on this prompt launches the


pop-up window. If not specified, the target user's username is used.

targetUserId

srcUserId

icon

prompt

<wdk:input>
The <wdk:input> tag is used to define a single-line text input field within a page. This tag must be used within a
<wdk:widgets> tag in a WDK page. The input widget allows users to enter small amounts of text (in HTML it
corresponds to the input form element of text or password type).
The <wdk:input> tag has one attribute:

| WDK Widgets | 156

Table 4-52:)Attributes of <wdk:input> Tag


Attribute Name

name

Required

Value

Yes

The name of the input field. The view stylesheet uses this name to identify the
widget. For example,

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:

| WDK Widgets | 157

Table 4-53:)Child Elements of <wdk:input> Tag


Child Element

Required

Value

No

This ID is the name of the generated input form element. If not specified,
the name attribute is used.

No

Tag used to attach the field to a Node in the <wdk:model>. This is


described further in "<wdktags:attachTo>""<wdktags:attachTo>" on
page 3-24.

No

The maximum length of the input element.

No

The initial value of the input element.

No

The label to display next to the input element.

No

If true, the widget is displayed in a way indicating that its value is re~
quired (for example, the label might be displayed in red). Defaults to
false.

No

If true, the field is presented as a password field. (For example, an


HTML password field might show * characters when users type in it.)
Defaults to false.

No

If true, the input field cannot be edited. (Certain UIs may indicate this
in various ways. For example, a read-only input field might have a gray
background.) Defaults to false.

No

If true, the input field is disabled. Defaults to false.

No

The size of the input element.

No

The rough size of the input field. Must be one of the following:

id

wdktags:attachTo

maxlength

value

label

required

password

readonly

disabled

size

sizeType

"small": Field displays 15 characters

| WDK Widgets | 158

Child Element

Required

Value

"medium": Field displays 30 characters


"large": Field displays 60 characters

By using sizeType values, you can arrange for fields containing


similar data to be displayed with similar on-screen lengths.

No

If true, the input field is displayed as text, not as an input element.


Default is "false", unless there is a model object (as described in
"Widgets and Model Objects").

No

If "true", a colon is appended to the label. (That is, if the label is


"Name", it is displayed as "Name:".) Default value is "false".

No

Adding element marks the widget as a numeric input field. It is described


further in "<number> Child Element" below.

viewOnly

useLabelTerminator

number

<number> Child Element


The <number> element indicates that the field is used to input a numeric value. It has the following subelements:
Table 4-54:)Child Elements of <wdk:link><field> Tag
Child Element

Required

Value

Yes

Specifies what types of numbers can be entered with the widget. Must
be one of the following:

type

"kPositiveInt": Feild accepts a positive integer.


"kInt": Field accepts an integer (positive, negative, or zero).
"kPositiveReal": Field accepts a positive real (floating point)
number.
"kReal": Field accepts any real number.
"kPercentage": Field accepts any real number in the range 0-100.
Note: If you enter any text other than one of these four values,
it causes a compilation error when the page is generated.

No

Alert text to display if the user enters an inappropriate number. If you


do not use an alert field, Saba displays a standard error message.

No

The name of a form element to copy the value from. A <field> must
have either a <value> or a <formfield> child, but not both.

alert

formField

| WDK Widgets | 159

The following example illustrates a variety of input fields defined within the <wdk:widgets> section of a WDK
page. Each input field is dynamically attached to a Node in the model.

<wdk:widgets>

<wdk:input name="param">

<wdktags:attachTo path="/info_service/param"/>

<id><wdktags:nodeRef path="name"/></id>

<value><wdktags:nodeRef path="value"/></value>

<size>45</size>

<sizeType>large</sizeType>

</wdk:input>

<wdk:input name="title">

<wdktags:attachTo path="/info_service"/>

<value><wdktags:nodeRef path="title"/></value>

</wdk:input>

<wdk:input name="username">

<wdktags:attachTo path="/info_service"/>

<value><wdktags:nodeRef path="username"/></value>

</wdk:input>

| WDK Widgets | 160

<wdk:input name="password">

<wdktags:attachTo path="/info_service"/>

<value><wdktags:nodeRef path="password"/></value>

<sizeType>small</sizeType>

</wdk:input>

<wdk:input name="pos_int">

<wdktags:attachTo path="/info_service"/>

<value><wdktags:nodeRef path="emp_number"/></value>

<number>

<type>kPositiveInt</type>

<!-- If bad number entered, display my localized error

message. -->

<alert>

<wdktags:i18n.label name="kBadNumberEntered"/>

</alert>

</number>

</wdk:input>

| WDK Widgets | 161

</wdk:widgets>

<wdk:impresence>
This widget displays a link that launches a popup window, which displays information about a particular user's instant
messaging handles. The window also displays links to open an instant-messaging conversation with that user, if
appropriate. (These links are only shown if the logged-in user and the targetted user have accounts on the same instant
messaging services.)
The <wdk:impresence> tag has one attribute:
Table 4-54:)Attributes of <wdk:impresence> Tag
Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:


Table 4-55:)Child Elements of <wdk:impresence> Tag
Child Element

Required

Value

Yes

The user ID of the person whose Instant Messenger information is being


displayed.

No

The user ID of the person who is contacting the target. (This is used to
determine which IM services the users have in common.) By default,
this is the logged-in user.

No

The icon to display for launching the pop-up window. If not specified,
a default icon is used.

No

Prompt to display for the icon. By default, the target user's username is
used.

targetUserId

srcUserId

icon

prompt

| WDK Widgets | 162

Example
The following code in a model page defines the widget:

<wdk:impresence name="imtest">

<targetUserId>emplo000000000000100</ targetUserId >

<!-- Use default values for other parameters -->

</wdk: impresence >


In the above case the default values are used for the other widget properties: the source user is the logged-in user, and
the default icon and link text are used. In the view page, the widget could be used under any <SCRIPT> tag:

<tr>

<td>

<xsl:apply-templates

select="$wdkWidget[@name='imtest']"/>

</td>

</tr>

<wdk:link>
The <wdk:link> tag is used to define a link within a page. This tag must be used within a <wdk:widgets> tag in
a WDK page.
This tag generates JavaScript code for activating the link. A <wdk:link> widget can be used to perform a set of
actions, such as:

submit the page


open a popup window
issue a callback call from a popup window
execute some client side JavaScript
close the window

This generated script calls the appropriate functions based on the set of actions.

| WDK Widgets | 163

A link tag can open a Saba application page. It can do this in several ways:

It can specify a WDK control page


It can specify a WDK model page. In this case, the control page for the model page containing the widget maps the
link to an appropriate control page.

Some pages aunch themselves, but with different command options. For example, an application page might let people
sort a table by various columns. When the user clicks a column heading, the page launches itself, with a parameter
specifying which column the page should be sorted by.
The problem is, an application page is uniquely specified by its control page (which, in turn, specifies the model and
view pages, the widget library, and so on.). However, the link widget is specified on the model page, and the model
page's code cannot know which control page might launch it (since a single model page can be used by several control
pages). For this reason, the WDK permits the <wdk:link> tag to specify a model page. In this case, the control page
must contain a <wdk:links> section that maps the model-page link to a link to a control page (as described in "Control
File""Control File" on page 3-5).
The <wdk:link> tag has one attribute:
Table 4-56:)Attributes of <wdk:link> Tag
Attribute Name

name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:

| WDK Widgets | 164

Table 4-57:)Child Elements of <wdk:link> Tag


Child Element

Required

Value

No

This id is the name (in the target code, for example, HTML) of the
generated link. If not specified, the value of the name attribute is used.

No

Tag used to attach the field to a Node in the <wdk:model>. This is


described in "<wdktags:attachTo>""<wdktags:attachTo>" on page 324.

No

The prompt (on mouseover).

No

If the link is displayed as an icon, the label text (if any) is shown next
to it. If it is displayed as a hyperlink, the label text is used as the link.

No

If present, the link is displayed as an icon.

No

Supported values are "link" (display as hyperlinked text) and "but~


ton" (display a button-like graphic). The default is "link".

No

Variables to submit to the page loaded when this link is clicked. There
may be zero, one, or more field tags.

No

A shorthand version of <field>. The node

id

wdktags:attachTo

prompt

label

icon

type

field

do
<do>action_name</do>
has precisely the same effect as

<field>
<name>actionKey</name>
<value>action_name</value>
</field>

Yes (see note) Actions to execute when this link is clicked.


action

Note: The fields are updated, and any popup window is launched
(and its actions run), BEFORE any of the actions are run.

| WDK Widgets | 165

Child Element

Required

Value
Actions are executed in document order until either a submit,
sabapage, or close action is called, or the function of a process
action returns false.
Note: An <action> tag is not required if there is an <href>
tag belonging to the <wdk:link>, or if there is a <popup> tag.

No

Equivalent to declaring

href
<action type="submit">
<href>[tag contents]</href>
</action>
as the last action for this link. (All <action> tags are executed, in or~
der, before the <href> tag is executed.)

No

Indicates whether the link is used to invoke a popup window.

No

The window used to open the link (used as the value of the HTML
target attribute). If omitted, the target is the current window.

No

Specifies the minimum width (in pixels) to allocate for the link widget.
The widget is at least this wide, even if the label text doesn't require that
much space.

popup

target

minWidth

None of the child elements of <wdk:link> have any attributes. However, the field, action, and popup child
elements can contain subelements, as described below.
<field> Child Element
The <field> element is used to specify HTTP parameters that should be passed to the page being launched. It has the
following subelements:

| WDK Widgets | 166

Table 4-58:)Child Elements of <wdk:link><field> Tag


Child Element

Required

Value

Yes

The name of the HTTP parameter. This does not have to correspond to
a Java page variable.

No

The text to use as the field's value. A <field> must have either a
<value> or a <formfield> child, but not both.

No

The name of a form element to copy the value from. A <field> must
have either a <value> or a <formfield> child, but not both.

name

value

formField

As noted above, you can also use <do>action</do> instead of


<field><name>actionKey</name><value>action</value></field>.
<action> Child Element
The action element is used to specify what happens when a user clicks the link. It is also used to specify the behavior
of a popup window, when it is the child of a <popup> tag (as described below, in "<popup> Child Element"). As noted
above, a link can have several <action> children. If so, the actions are taken one at a time, in the order they appear.
However, if an action is of type submit, sabapage, or close, no further actions are taken after it. If an action of
type process returns false, no further actions are taken.
The tag has no attributes. However, it has the following subelements:
Table 4-59:)Child Elements of <wdk:link><action> Tag
Child Element

type

Required

Value

No

Specifies the type of action taken. Possible values include:

submit: (default) Launch the URL specified. The <action>


element must have an <href> child element.
callback: Only appropriate if the link is within a popup window
(that is, a window that was created by some other link's <popup>
action). The main window's callback function is called. (This is de~
scribed further in "<popup> Child Element".)
process: Run a Javascript function. (Note that this is run on the
client browser. You should be careful not to write code that intro~
duces client compatibility problems.) The <action> element must
have a <function> child element. If the process returns false,
no further <action> tags are run.
close: Closes the current window.
back: The current window goes "back" to the previous page dis~
played.

| WDK Widgets | 167

Child Element

Required

Value

href

portal: This may only be used if the link is on a portlet page. It


causes the portlet to be repainted.

This tag is required if the <type> is submit, or if there is no <type>


tag. (If no type is explicitly specified, the type is taken to be submit,
and an <href> tag is required.)
If the <type> is submit (or no <type> is explicitly specified), the
href specifies the URL to launch. It may be the URL of any web page,
or a WDK application page. If the URL specifies a model page (.xml),
the source's control page must map the link to a target control page.
The href tag can have a single attribute, useSite. If useS~
ite="true" (the default), or the attribute is missing, the URL is pre~
pended with the Saba site's document root. If useSite="false",
the URL is used as-is (allowing you to link to pages on another web
site).

<function>

This tag is required if the <type> is process. The tag has the follow~
ing subtags:

<name>: (required) The name of the Javascript function to launch.


<arg>: Argument to pass to the Javascript function. By default, this
is a Javascript variable. However, if the <arg> tag has a
type="String" attribute, the contents of the <arg> tag are
passed as a string literal. There may be zero, one, or more <arg>
tags.

<popup> Child Element


The <popup> element is used to define a popup window. If a link contains a popup tag, the following sequence is
normally followed:
1. The popup window frame is created.
2. The <action> tags that are children of the <popup> tag are executed, in order. The last one of these should be
a submit or sabapage tag, which loads a page in the popup window.
3. The popup window usually has its own <wdk:link> tag, which contains an <action> tag of type callback.
4. When the popup window's callback tag executes, any output values are copied to the designated fields in the
calling WDK page. (These are specified in the <popup> tag.)
5. After the popup window executes a callback action, the original wdk:link tag's actions are executed.
Note that when the popup window executes the callback action, it does not immediately close. It can run other
actions, or remain open until the user closes it. If you want to force the window to close, put a close action tag after
the callback tag.
Also note that the popup window does not have to have a callback action. If it does not, then the original wdk:link
tag's actions are never executed. However, this is perfectly legal. For example, you might define an "info" link that pops
up a window showing detailed information about something. The window stays open until the user chooses to close it.
The <popup> tag does not have any attributes. It has the following child elements:

| WDK Widgets | 168

Table 4-60:)Child Elements of <wdk:link><popup> Tag


Child Element

Required

Value

No

This has the same syntax and behavior as an <action> tag that belongs
to <wdk:link> (described in "<action> Child Element").
An <action> that belongs to a <popup> must be of type process,
sabapage, or submit. (sabapage and submit tags load a page
into the popup window, not the original window.) You can use process
actions to perform any operations necessary before launching the popup
window. (If any of the process actions returns "false", the entire
link aborts: no further popup actions are run, the popup window is not
opened, and the actions that belong directly to the <wdk:link> tag
does not run.)

action

Note: The action tag is required unless the <popup> tag has
an <href> child.

No

Equivalent to declaring

href

<action type="submit">
<href>[tag contents]</href>
</action>
as the last action for this popup. (All <action> tags are executed, in
order, before the <href> tag is executed.)

No
input

Specifies a value to be passed, as a parameter, to the page opened in the


popup window. Each <input> tag must have a <name> child, and
either a <value> or a <formField> child:

No
output

<name>: Name of the parameter to pass to the popup window.


<value>: Value of the parameter.
<formField>: Name of a form field in the original window. Its
value is passed to the popup as the value of this parameter.

Specifies a value returned by the popup window. When the popup win~
dow executes its <callback> action, each of the popup's output values
is available to the original page. It can use them if it declares correspond~
ing <output> tags here.
Each <output> tag must have a <name> child and a <formField>
child:

<name>: Name of the parameter returned by the popup window.

| WDK Widgets | 169

Child Element

Required

Value

<formField>: Name of a form field in the original window. The


value returned by the popup window is written to this field.

No

Name of the popup window. This is displayed in the window's title bar.

No

Name of a Javascript popup window variable. This is useful if you want


to be able to manipulate the popup window from Javascript code in the
source page.

No

If present, specifies a JavaScript window feature string. This tag has


these optional child tags:

windowTitle

window_name

windowFeatures

<type>: Either standard (the default, for a standard window)


or custom.
<featureString>: If <type> is custom, there must be a
<featureString> tag. It specifies a JavaScript feature string for
the popup window.
(Note that
<windowFeatures/>
and
<windowFeatures>
<type>standard</type>
</windowFeatures>

while perfectly legal, are no different from having no windowFea~


tures tag at all.)

| WDK Widgets | 170

Example 1: Link to an HTML Page


This example illustrates how to make a plain-text link to an HTML page. The page is launched in a new window. The
hyperlink is displayed as text, and the text is hard-coded in the model page (instead of being loaded from a resource
bundle).

<wdk:link name="payrollSite">

<label>MyCorp's payroll site</label>

<action>

<!-- "type" is implicitly "submit", if not specified -->

<href usesite="false">http://payroll.my-corp.com/</href>

</action>

<target>payroll_window</target>

</wdk:link>
This is translated into HTML code along these lines:

<a href="http://payroll.my-corp.com/" target=payroll_window>MyCorp's payroll


site</a>
Example 2: A Simple Button
The following fragment defines a link called deleteButton. This link is attached to a Node in the model for one of
its field values. It also defines two additional fields, which are sent when the link is submitted. One of those fields has

| WDK Widgets | 171

a fixed value, and the other's is the value of a Java expression. When the user clicks the link, a WDK page is loaded,
and is passed the specified values.

<wdk:link name="del e

teButton">

<wdktags:attachTo t

h="wdk:collection/info_service"/>

<!-- This field's value is extracted from this model page.-->

<field>

<name>id</name>

<value><wdktags:nodeRef path="id"/></value>

<!-- This path is relative to the path set by the

wdktags:attachTo node above-->

</field>

<!-- This field's value is hard-coded into the page: -->

<field>

<name>actionKey</name>

<value>delete</value>

</field>

| WDK Widgets | 172

<!-- This field's value is read from a Java variable: -->

<field>

<name>userId</name>

<value><xsp:expr>user.getId()</xsp:expr></value>

</field>

<action> <!-- Only one action: load the specified page -->

<href>iss_view_info_services.xml</href>

<!-- "type" is implicitly "submit" -->

<!-- Page is specified relative to document root -->

</action>

<!-- We load the label and prompt from a localized resource; see

"<wdktags:i18n.label>""<wdktags:i18n.label>" on page 3-29 -->

<label><wdktags:i18n.label name="kI18n9000Delete"/></label>

<prompt><wdktags:i18n.label name="kI18n9000Delete"/></prompt>

</wdk:link>
Note that this page links to a WDK model page. The control page for this source page must map the link to a link to a
control page. It has code something like this:

<wdk:links>

| WDK Widgets | 173

<wdk:link model="iss_view_info_services.xml"

control="iss_view_info_services.rdf"/>
</wdk:links>
This indicates that every model page link to the page iss_view_info_services.xml should be treated as a link
to the control page iss_view_info_services.rdf.
When the user clicks on the delete button, the source page launches the target WDK page. As always, the source's out
variables are passed to the target page as parameters. In addition, the target is passed three parameters specified by this
link:

id: The value passed is the value in the source page's wdk:collection/info_service/id node.
actionKey: The value "delete" is passed.
userId: When the source page is built by the WDK engine, it executes the Java code within the <xsp:expr>
tag, and replaces the tag with the code's output. The resulting text (say, "fbaggins") is used as the value of the
userId parameter.

The target page, presumably, has input parameters named "id", "actionKey", and "userId", which it uses to
access the passed values.
Example 3: Executing JavaScript Before Loading Page
In this example, when the link is clicked on, it first executes a JavaScript function. This function (whose code is not
shown here) displays a confirmation dialogue to the user. If the user clicks "OK", the link continues to its next action,

| WDK Widgets | 174

loading a successor WDK page. If the user clicks "Cancel", the function returns "false", and the link stops executing
code-it does not open the successor page.

<wdk:link name="confirmBeforeSubmit">

<label>Submit</label>

<pr

ompt>Click here to submit</prompt>

<!-- The order of the actions is important! -->

<action>

<type>process</type> <!-- Execute a JavaScript function -->

<function>

<name>confirm</name> <!-- Name of JavaScript function -->

<arg>'Do you really want to submit?'</arg>

<!-- 1st argument to the JavaScript function -->

</function>

</action>

<!-- If the process does not return "false", the next action is

executed. -->

<action>

<href>nextPage.xml</href>

| WDK Widgets | 175

<!-- "type" is implicitly "submit" -->

<!-- Page is specified relative to document root -->

</action>

</wdk:link>
As with example 2, the source's control page has to map the nextPage.xml link (a link to a model page) to a link to
an appropriate control page.
Example 4: Link Opening a Popup Window
In this example, the link opens a popup window, passing the value of the source page's client_bunit field. The
popup window launches a WDK page, which presumably has its own link object, containing a callback action. When

| WDK Widgets | 176

the user activates the link in the popup window, the value of its output business_unit variable is copied to the
source page's client_bunit field.

<wdk:link name="open_popup1">

<label>Select business unit</l

abel>

<popup>

<input> <!-- Pass this parameter to the popup window -->

<name>business_unit</name>

<formField>client_bunit</formField>

<!-- value for parameter -->

</input>

<output> <!-- The popup window returns a value... -->

<name>business_unit</name>

<formField>client_bunit</formField>

<!-- ...which is written to this field -->

</output>

<!-- This action specifies what page is loaded in the popup


window. -->

<action>

<href>wdkBusinessUnitPicker.xml</href>

| WDK Widgets | 177

<!-- "type" is implicitly "submit" -->

<!-- Page is specified relative to document root -->

</action>

</popup>

<!-- After the popup window calls its callback function, the

source link's action is executed. -->

<action>

<type>submit</type>

<href>nextPage.xml</href>

<!-- "type" is implicitly "submit" -->

<!-- Page is specified relative to document root -->

</action>

</wdk:link>
As with the other examples, the source control page must map the "model" (.xml) links to links to control pages (.rdf).

| WDK Widgets | 178

The page loaded by the popup window presumably has a link object of its own, along these lines:

<wdk:link name="done">

<label>Done</label>

<action>

<type>callback</type>

</action>

<action> <!-- Close the popup window when the user clicks "done" -->

<type>close</type>

</wdk:link>
The above link calls the auto-generated callback function in the main window (copying the output variable from the
popup window to the main window's client_bunit field), then close the popup window.

<wdk:list>
This widget generates a list of selectable items. The widget can present these items as a set of radio-buttons, a set of
checkboxes, or a selectable list of entries. The list is set up as an HTML form element. When the user launches a new
application page (by clicking on a link), the selected elements in the list is passed to the new page as HTML parameters.
Each entry in the list is identified by an <option> tag. When the page is transformed by the WDK engine, each of
these tags is automatically wrapped inside a <wdk:widget> tag so a view sheet can easily customize the look and
layout of each option. The name attribute of the <wdk:widget> tag for the option is the name attribute of the original
<wdk:widget> tag + "_option".
The <wdk:list> tag has one attribute:
Table 4-61:)Attributes of <wdk:list> Tag
Attribute Name

name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

| WDK Widgets | 179

The widget has the following child elements:


Table 4-62:)Child Elements of <wdk:list> Tag
Child Element

Required

Value

No

This id is the name of the generated element. If not specified, the value
of the widget's name attribute is used.

No

Tag used to attach the field to a Node in the <wdk:model>. This is


described further in "<wdktags:attachTo>""<wdktags:attachTo>" on
page 3-24.

No

The label to display next to the element.

No

If true, the widget is displayed in a way indicating that its value is re~
quired (for example, the label might be displayed in red). Defaults to
false.

No

Indicates what type of list this is. Available list types are "select",
"radio", and "checkbox".

No

Number of elements displayed without scrolling. This is only relevant


if the type is "select".

No

Entries in the list. Each option tag must have two children:

id

wdktags:attachTo

label

required

type

size

option

No
selection

<value> The value submitted if this option is selected from the


list.
<text> The text to display for this list entry.

Indicates which option or options are selected when the application page
is first loaded. The <selection> tag has one or more <value>
subelements. These <value> tags contain text corresponding to <op~

| WDK Widgets | 180

Child Element

Required

Value
tion><value> text. The options whose values are listed here are se~
lected when the page is loaded.

No

Defines an action or actions to take when the user changes the selection
in the list. This is described further in "List Events".

No

If this tag is "true", the list is displayed as (non-selectable) text, not


as an HTML form. Default is "false", unless there is a model object
(as described in "Widgets and Model Objects").

No

If "true", a colon is appended to every label in the list (for example,


if the label is "Taxable", it is displayed as "Taxable:"). Defaults
to "false".

No

Valid values are "left" or "right". Indicates whether the item labels
(that is, the <text> elements within each <option> element) should
be printed on the left or right side of the checkboxes or radio buttons.
The default is "left" (labels printed to the left of the buttons).

No

Only applies to "select" lists. If true (default), show a line for "select
none". If false, only show lines for the list values you provide, forcing
the user to pick one.

event

viewOnly

useLabelTerminator

labelOrder

showBlank

Ignored if list type is "check".

List Events
A list can define a series of actions to take when the user changes the selection in the list. The list does this by defining
an <event> tag with this format:

<event> <type>action</type> [one or more <action> tags]</event>


The <action> tags are like those used by the <wdk:link> widget (as described in "<wdk:link>"). The actions must
be of type "process", "submit", "close", or "back". (As with <wdk:link>, the action sequence ends if a
process returns false, or if an action of type "submit", "close", or "back" is executed.)

| WDK Widgets | 181

Example: Simple List


In this example, a hard-coded list of three radio elements is displayed:

<wdk:list name="language_list">

<id>language</id>

<label>Please choose a language:</label>

<type>radio</type>

<!-- Entries to display in the list -->

<option>

<value>en_US</value>

<text>English</text> <!-- Display name -->

</option>

<option>

<value>de_DE</value>

<text>German</text>

</option>

<option>

<value>fr_FR</value>

<text>French</text>

| WDK Widgets | 182

</option>

<!-- Default selection-->

<selection>

<value>en_US</value> <!-- US-English by default -->

</selection>

</wdk:list>
The list is displayed as a radio list with three options: "English", "German", and "French". ("English" is selected when
the page loads.) The user can change the selection by clicking on any of the radio buttons. When the user leaves the
page (by clicking on a link), the selected value is passed to the next page as an HTML parameter-the key is "language"
(the name of the list), and the value is the value associated with whichever list entry is selected. (For example, if the
user selects "German" then loads a new page, the new page is passed an HTML parameter named "language" with
the value "de_DE".)
Example: Dynamically Generated List
This list's contents are set dynamically, based on the contents of the model portion of the page. This list displays the
information service templates for the system. The list is attached to the model, which in this case contains the list of

| WDK Widgets | 183

known templates. The <wdktags:repeat> tag is used to iterate over the templates and creating an option for each,
building the options in the list.

<wdk:list name="templateSelector">

<wdktags:attachTo path=""/> <!-- attach to <wdk:model> -->

<type>select</type>

<!-- Make one <option> tag for each <info_service_template>


node: -->

<wdktags:repeat name="templateOption" path="info_service_template">

<option>

<!-- Fetch value from

wdk:main/info_service_template/name -->

<value>

<wdktags:nodeRef path="name"

source="templateOption"/>

</value>

<!-- Use same text for "name" -->

<text>

<wdktags:nodeRef path="name"

source="templateOption"/>

| WDK Widgets | 184

</text>

</option>

</wdktags:repeat>

<!-- We assume there is a Java variable named "defaultSelection"

whose value is the name of the default selection. -->

<selection>

<value><xsp:expr>defaultSelection</xsp:expr></value>

</selection>

</wdk:list>

<wdk:lovPicker>
The <wdk:lovPicker> can be used to place a list-of-values (LOV) picker (a combination of a dropdown menu, a
link, and a popup window displaying a finder) on a page. That is, the page containing a sabaPicker displays a
dropdown menu and a link. When the user activates the link, the popup window containing the finder is launched. (It is
much like a <wdk:sabaPicker>, except that it displays a drop-down menu instead of a text entry field.
The widget creates two form elements: a drop-down menu for choosing the selection, and a hidden field for storing the
ID of the selection. The name of the hidden field is specified by the text in the widget's <id> child tag. If there is no
<id> tag, the widget's name attribute is used.
The <wdk:lovPicker> tag has one attribute:
Table 4-64:)Attributes of <wdk:lovPicker> Tag
Attribute Name

name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

| WDK Widgets | 185

The widget has the following child elements:


Table 4-64:)Child Elements of <wdk:lovPicker> Tag
Child Element

callbackFn

Required

Value

No

The name of a JavaScript function to launch when the user clicks


the Done link in the popup window. The function must have the
signature:

function(listName, value, option,


selectedOptionIndex)
where both value and option are strings containing comma-delimited
lists.

| WDK Widgets | 186

Child Element

Required

Value
The widget does not examine any return value from the function.

No

If true, users can type directly in the text box instead of launching
the popup window. Defaults to false.

No

This id is the name of the generated element. If not specified, the


value of the widget's name attribute is used.

No

The label to display next to the element.

No

Text prompt to show (for example, with a ToolTip) when the


pointer hovers over the link.

No

If true, the widget is displayed in a way indicating that its value


is required (for example, the label might be displayed in red). De~
faults to false.

No

Initial value of the selection. Defaults to the empty string (that is,
the first element in the list).

No

If "true", a colon is appended to every label in the list (for example,


if the label is "Taxable", it is displayed as "Taxable:"). Defaults
to "false".

No

Initial value of the hidden field. Defaults to the empty string.

No

If "true", the widget is displayed as text, and is not editable. Default


is "false", unless there is a model object (as described in "Widgets
and Model Objects").

No

Specifies the picker's width. Possible values are default (normal


picker width) or wide.

No

Specifies the height of the picker's popup window. Height is specified


in number of pixels. The value should be a number between 300

enable

id

label

prompt

required

value

useLabelTerminator

value

viewOnly

widthType

| WDK Widgets | 187

Child Element

Required

Value
and 600 (inclusive). Defaults to 300.

height

No

Tag used to attach the field to a Node in the <wdk:model>. This


is described further in "<wdktags:attachTo>""<wdktags:attachTo>"
on page 3-24.

wdktags:attachTo

<wdk:multistep>
This widget is used to create a multi-step application page. The widget displays any of several smaller model files within
itself. The user can move from one page to the next in order. This enables you to build "wizard"-style pages where users
follow a sequence of steps.
The <wdk:multistep> tag has one attribute:
Table 4-65:)Attributes of <wdk:multistep> Tag
Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:


Table 4-66:)Child Elements of <wdk:multistep> Tag
Child Element

Required

Value

No

If true, the user must follow the steps in order. The widget displays
Next and Previous buttons to let the user advance to the next stage or
go back to the previous one, but users cannot jump to any arbitrary step.

forced

If false (the default setting), the user can follow the steps in order,
but can also jump ahead or behind to any other step.

No
showButton

If true, the widget displays Next and Previous buttons regardless of


whether the <forced> is true.

| WDK Widgets | 188

Child Element

Required

Value
If false, the widget displays Next and Previous buttons if <forced>
is true, but not if <forced> is false.

Yes

The ID of the currently selected stage of the widget. Must be the ID of


one of the <action> subelements.

Yes

Specifies one of the sub-pages displayed in the widget. There is one


<action> element for each sub-page, listed in order (the first <ac~
tion> is step 1, and so on). The format of this tag is described in
"<action> Child Element".

selected

action

<action> Child Element


Each <action> node specifies one of the subpages displayed in the multistep widget. Each <action> has the
following child elements:
Table 4-68:)Child Elements of <wdk:multistep><action> Tag
Child Element

Required

Value

Yes

ID for this step. This is used by the <selected> node to identify the
currently active step.

Yes

Label for the step.

Yes

Model file to load for this step.

No

A parameter to pass to the called page when the link is clicked. You may
have as many or as few <field> tags as you wish for each <link>.
Every <field> must have two children: a <name> with the parameter's
name, and a <value> with the parameter's value.

id

label

href

field

<wdk:multisection>
This widget is used to create a multi-sectionp application page. The widget displays any of several smaller model files
within itself, each as a tabbed section. The user can jump to whichever tab he or she wants.
The <wdk:multisection> tag has one attribute:

| WDK Widgets | 189

Table 4-69:)Attributes of <wdk:multisection> Tag


Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:


Table 4-70:)Child Elements of <wdk:multisection> Tag
Child Element

Required

Value

Yes

The ID of the currently selected stage of the widget. Must be the ID of


one of the <action> subelements.

Yes

Specifies one of the sub-pages displayed in the widget. There is one


<action> element for each sub-page, listed in order (the first <ac~
tion> is step 1, and so on). The format of this tag is described in
"<action> Child Element".

selected

action

<action> Child Element


Each <action> node specifies one of the subpages displayed in the multistep widget. Each <action> has the
following child elements:
Table 4-71:)Child Elements of <wdk:multisection><action> Tag
Child Element

Required

Value

Yes

ID for this step. This is used by the <selected> node to identify the
currently active step.

Yes

Label for the step.

Yes

Model file to load for this step.

No

A parameter to pass to the called page when the link is clicked. You may
have as many or as few <field> tags as you wish for each <link>.
Every <field> must have two children: a <name> with the parameter's
name, and a <value> with the parameter's value.

id

label

href

field

| WDK Widgets | 190

<wdk:pageText>
This widget is used to include page-specific text (such as page instructions) from the page text resource bundle.
The <wdk:pageText> tag has one attribute:
Table 4-72:)Attributes of <wdk:pageText> Tag
Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:


Table 4-73:)Child Elements of <wdk:pageText> Tag
Child Element

Required

Value

No

Tag used to attach the field to a Node in the <wdk:model>. This is


described further in "<wdktags:attachTo>""<wdktags:attachTo>" on
page 3-24.

No

This id is the name (in the target code, for example, HTML) of the frame
containing the included page. If not specified, the value of the widget's
name attribute is used.

Yes

The resource whose value should be included in the page. You may have
several <resource> tags in the widget. The text of those resources is
automatically separated by <BR /> tags.

No

A bind variable needed by the resource. You may have several <bind>
tags if necessary.

wdktags:attachTo

id

resource

bind

| WDK Widgets | 191

Example

<wdk:pageText name="pageText">

<wdktags:attachTo path="."/>

<id>date_test</id>

<!-- Use the values of these resources: -->

<resource>kTeamLearningHistory1</resource>

<resource>kTeamLearningHistory2</resource>

<bind><wdktags:nodeRef path="employee/fullname"/> </bind>

</wdk:pageText>

<wdk:pageTitle>
This widget specifies several properties for the application page. It specifies the page's title, what Saba component is
used by the page, and several other properties of the page (such as whether it should provide spell checking or audit trail
functionality).
The <wdk:pageTitle> tag has one attribute:
Table 4-74:)Attributes of <wdk:pageTitle> Tag
Attribute Name

name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:

| WDK Widgets | 192

Table 4-75:)Child Elements of <wdk:pageTitle> Tag


Child Element

Required

Value

No

The page title.

Title

Note: If a title is not provided, the widget must define both


<ComponentName> and <ComponentReference>.

No
ComponentName

The component used by this page. If the component is internationalized,


the page automatically provides the internationalization link.
The component name is also used to determine the page title, if
<Title> is not present.

No

The instance ID of this page's component. Used to determine whether


an audit trail should be generated for actions on this page. Also used to
determine the page title, if <Title> is not present.

No

A description of the application page.

No

If true, show the audit link.

No

Reference to page opened by the audit link.

No

If true, show the attachments link.

No

Reference to page opened by the attachments link.

No

User ID of the owner of the attachments. This should match the owner
of the attachments widget.

No

If false, the internationalization icon is not be shown even if the


component is internationalizable. If true or not specified, internation~
alization icon is shown if (and only if) the component is internationaliz~
able.

ComponentReference

TitleDescription

auditOn

auditHref

attachmentOn

attachmentHref

attachmentOwnerId

internationalizeOn

| WDK Widgets | 193

Child Element

Required

Value
Note: This node has no effect if the page's underlying component
is not internationalizable. The node is only used to specify that the
internationalization link should not be shown for an otherwise in~
ternationalizable icon.

No

If true, page displays the notes link.

No

If true, page displays the reports link.

No

If true, page displays the spelling-check link.

No

If true, show page legend.

No

If true, page steps must be taken in order, and page displays a Next
link to move to the next step. If false, page steps are links, and user
can jump to any step by clicking the link.

Yes

The currently active step. The contents of this node must be the ID of
one of the action subelements.

No

Specifies the resource keys for create and edit detail pages. This can
have the following three subelements:

notesOn

reportOn

spellingCheckOn

legendVisible

forced

selected

titleKeys

create: Resource bundle for create page.


edit: Resource bundle for edit page.
bundle: Resource bundle for both create and edit pages.

If the resource bundle for either the create or edit page is not specified
(or there is no titleKeys node), the page's default resource bundle
is used.

| WDK Widgets | 194

Example

<wdk:pagetitle name="pageTitle1">

<title>Test Page</title>

<legendVisible>true</legendVisible>

<attachmentOn>true</attachmentOn>

<attachmentOwnerId>gdefn000000000001011</attachmentOwnerId>

<notesOn>true</notesOn>

<reportOn>true</reportOn>

<auditOn>true</auditOn>

<spellingCheckOn>true</spellingCheckOn>

<attachmentHref>link_attachment</attachmentHref>

<auditHref>link_audit</auditHref>

<notesHref>link_notes</notesHref>

<reportHref>link_report</reportHref>

<componentName>testcomponent</componentName>

<componentInstance>insta00000022222</componentInstance>

<titleKeys>

<create>kI18n80017ComponentCreateTitle</create>

| WDK Widgets | 195

<edit>kI18n80017ComponentEditorTitle</edit>

<bundle>analytics</bundle>

</titleKeys>

</wdk:pagetitle>

<wdk:pageWidget>
This widget enables you to include one WDK page in another. The included page receives all the same request parameters
as the outer page, as well as all the other HTTP parameters. In addition, you can explicitly pass special parameters to
it.
The <wdk:pageWidget> tag has one attribute:
Table 4-76:)Attributes of <wdk:pageWidget> Tag
Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:


Table 4-77:)Child Elements of <wdk:pageWidget> Tag
Child Element

Required

Value

Yes

A reference to a WDK model page. As with all links to model pages,


your control page must have a <wdk:link> tag translating it into a
reference to a control page.

No

An HTTP parameter to pass to the called page. The called page receives
the loading page's request object and parameters. In addition, you can
explicitly pass parameters with <field> tags. Each <field> has two
children:

href

field

<name>: The name of the passed HTTP parameter.


<value>: The value of the parameter. Only strings are supported.
You may pass as many or as few <field> parameters as you like.

| WDK Widgets | 196

Only the child's model page is executed (not the view or control pages). The included page's contents are inserted into
the including page in the following way:

The contents of the included page's <wdk:head> are copied into the including page, including <wdk:label>
nodes, and so on.
The contents of the included page's <wdk:model> element are copied into a <wdk:includedPage
name="widget_name"> element, which is placed inside the including page's <wdk:model> element.
The contents of the included page's <wdk:widgets> element are copied into the including page's <wdk:widgets>
element.

Including the page in this way, instead of executing a separate view transformation on the included page, significantly
improves performance. However, the consequence of this approach is that the pageWidget cannot be attached (that is,
you must not use <wdktags:attachTo> in it, and you cannot include a pageWidget inside a <wdk:table>,
<wdk:finderResult>, and so on., widgets).

| WDK Widgets | 197

Brief Usage Example

<wdk:pageWidget name="userInfoPage">

<href>userInfo.xml</href> <!-- Model page to load -->

<field>

<name>user</name>

<value>dvader</value>

</field>

<field>

<name>fullInfo</name>

<value>true</value>

</field>

</wdk:pageWidget>
Full Example
The includingPage.xml looks like the following (the pageWidget node is in italics):

<wdk:page xmlns:wdk="http://www.saba.com/XML/WDK">

...

<wdk:head>

<wdk:title>Initiative Detail</wdk:title>

| WDK Widgets | 198

<wdk:labels>

<wdk:label name="createFormPageHeading">

<wdktags:i18n.label name="kI18n0018createFormPageHeading"/>

</wdk:label>

...
</wdk:labels>

</wdk:head>

<wdk:form method="POST">

<wdk:model>

<wdktags:execute commandObj="anObj"/>

...

</wdk:model>

</wdk:form>

<!-- list of all widgets -->

<wdk:widgets>

<!-- start General Information -->

<wdk:input name="initiativeStatus">

<wdktags:attachTo path="/InitiativeDetail/status"/>

| WDK Widgets | 199

<id><xsp:expr>detail.getStatus().getName()</xsp:expr></id>

<size>20</size>

<value><xsp:expr>enumUtil.getI18nName( detail.getStatus()
)</xsp:expr></value>

<viewOnly>true</viewOnly>

</wdk:input>
<wdk:pageWidget name="sections">
<field>
<name>viewOnlyMode</name>

<href>sections.xml</href>

<value> <xsp:expr>viewOnlyMode</xsp:expr></value>
</field>
<field>
<name>name1</name>
<value> <xsp:expr>name1</xsp:expr></value>
</field>
<field>
<name>createMode</name>
<value>
<xsp:expr>createMode</xsp:expr></value>
</field>
<field>

| WDK Widgets | 200

<name>initiativeId</name>
<value><xsp:expr>initiativeId</xsp:expr></value>
</field>
</wdk:pageWidget> </wdk:widgets>

</wdk:page>

</xsp:page>
The included page (the model page sections.xml) looks like:

<wdk:page xmlns:wdk="http://www.saba.com/XML/WDK">

<wdk:head>

<wdk:labels>

<wdk:label name="titleLabel">&#187; Sections</wdk:label>

<wdk:label name="sectionPageText"><wdktags:i18n.label

name="kI18n0018SectionPageText"/></wdk:label>

<wdk:label name="raterPageText"><wdktags:i18n.label

name="kI18n0018RatersPageText"/></wdk:label>

</wdk:labels>

</wdk:head>

<wdk:form method="POST">

<wdk:model>

<wdktags:execute manager="SectionKindCommandManager" command="findSections">

| WDK Widgets | 201

<param name="initiativeId" expr="initiativeId" mode="in" type="String"/>

<param name="doIt" expr="doIt" mode="in" type="String"/>

<param name="sectionList" expr="sectionList" mode="in-out" type="List"/>

</wdktags:execute>

</wdk:model>

</wdk:form>

<!-- list of all widgets -->

<wdk:widgets>

<wdk:frameworkLink name="addSectionLink">

<mainPage>initiativeDetail.xml</mainPage>

<label><wdktags:i18n.label name="kI18nXXXXXAdd"/></label>

<prompt><wdktags:i18n.label name="kI18nXXXXXAdd"/></prompt>

<popup>

<input>

<name>sessionKey</name>

<value><xsp:expr>sessionKey</xsp:expr></value>

</input>

| WDK Widgets | 202

<input>

<name>reviewFormName</name>

<value><xsp:expr>name1</xsp:expr></value>

</input>

<input>

<name>initiativeId</name>

<value><xsp:expr>initiativeId</xsp:expr></value>

</input>

<action>

<type>submit</type>

<href>sectionTypes.xml</href>

</action>

<windowFeatures>

<type>custom</type>

| WDK Widgets | 203

<featureString>width=600,height=400,resizable=yes,scrollbars=yes</featureString>

</windowFeatures>

</popup>

</wdk:frameworkLink>

</wdk:widgets>

</wdk:page>
After execution, the generated model page looks like this:

<wdk:page xmlns:wdk="http://www.saba.com/XML/WDK">

...

<wdk:head>

<wdk:title>Initiative Detail</wdk:title>

<wdk:labels>

<wdk:label name="createFormPageHeading">Create Form Page Heading


text</wdk:label>

</wdk:labels>

<wdk:labels> <!-- these labels are from the included page -->

<wdk:label name="titleLabel">&#187; Sections</wdk:label>

<wdk:label name="sectionPageText">Section Page Text</wdk:label>

| WDK Widgets | 204

<wdk:label name="raterPageText">Rater Page Text</wdk:label>

</wdk:labels>

</wdk:head>

<wdk:form method="POST">

<wdk:model>

<!-- result of executing the anObj command -->

. <wdk:includedPage name="sections.xml">

<!-- result of executing the findSections Command -->

</wdk:includedPage>

</wdk:model>

</wdk:form>

<wdk:widgets>

<wdk:widget name="addSectionLink"> <!-- this widget is from the included


page -->

...

</wdk:widget>

</wdk:widgets>

</wdk:page>

| WDK Widgets | 205

</xsp:page>
The view page that accompanies includingPage.xml must do the following:
1. Import the view page that goes with the included page:

<xsl:import href="sections.xsl"/>
2. Use <xsl:apply-templates select="wdk:includedPage"/> to place the contents of the includedPage.
The view sheet that goes with the included page follows a slightly different guideline from other view pages:
1. The view page does not need to import the default view sheet, since the view sheet of the including page already
imports it.
2. Instead of containing a template to match wdk:model, it must contain a template to match wdk:includedPage:

<xsl:template match="wdk:includedPage">

<wdk:parameters>
The <wdk:parameters> tag can be used to aid debugging by displaying the ServletRequest parameters on the
page.
The <wdk:parameters> tag has one attribute:
Table 4-78:)Attributes of <wdk:parameters> Tag
Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has no child elements.


When the widget is placed, it produces a display like this:

Figure 4-2:) User Interface for <wdk:parameters> Widget

| WDK Widgets | 206

Example

<wdk:parameters name="custom" />

<wdk:presence>
Used to indicate whether a particular user is available through SabaDialog. The widget inserts JavaScript code that
graphically indicates, in real time, whether the user is available.
The <wdk:presence> tag has one attribute:
Table 4-79:)Attributes of <wdk:presence> Tag
Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:


Table 4-80:)Child Elements of <wdk:presence> Tag
Child Element

Required

Value

Yes

Tag used to attach the field to a Node in the <wdk:model>. This is


described further in "<wdktags:attachTo>""<wdktags:attachTo>" on
page 3-24.

wdktags:attachTo

Note: The customFields widget must be attached to the


<customFields> element generated by a customFieldXM~
LProducer object's visit() method.

No
userId

The internal ID of the user who is being tracked (for example, "em~
pl0000001000"). If this is supplied, <userName> must also be
supplied.
If this tag is not supplied, the current user is tracked.

No
userName

The name of the user who is being tracked. If this is supplied,


<userId> must also be supplied.
If this tag is not supplied, the current user is tracked.

| WDK Widgets | 207

Example

<wdk:model>

<employees>

<employee>

<id>emplo0001</id>

<name>User One</name>

</employee>

<employee>

<id>emplo0002</id>

<name>User Two</name>

</employee>

</employees>

</wdk:model>

<!-- ... -->

<wdk:widgets>

<wdk:presence name="presence">

<wdktags:attachTo path="employees/employee"/>

<userID><wdktags:nodeRef path="id"/></userID>

| WDK Widgets | 208

<userName><wdktags:nodeRef path="name"/></userName>

</wdk:presence>

<wdk:widgets>

<wdk:promptForSave>
This widget keeps people from leaving a WDK page without saving their work. When you create a promptForSave
widget, you specify two things:

<wdk:link> widgets on the model page that are "safe" to click on (for example, because they save the contents
of the input fields), and
Widgets on the model page that the save widget should monitor.

If the user clicks on any <wdk:link> widget except the ones designated as "safe", the page checks all the monitored
widgets. If the user has changed any of them from their initial values (for example, selected a different item in a list,
typed text into a field, and so on.), the page pops up a dialog box asking the user if he or she intends to discard changes.
If the user selects "OK", the link functions normally. If the user selects "Cancel", the link has no effect, and the user
stays on the page he or she was on.
If a model page includes a <wdk:promptForSave> widget, it functions automatically on every application page
that uses the model. The view page does not need to include the widget explicitly.
The <wdk:promptForSave> tag has one attribute:
Table 4-81:)Attributes of <wdk:promptForSave> Tag
Attribute Name

Required

Value

Yes

The name of the widget. This attribute is required, but is not currently used
elsewhere in the WDK (since the View page does not place this widget expli~
citly).

name

The widget has the following child elements:


Table 4-82:)Child Elements of <wdk:promptForSave> Tag
Child Element

Required

Value

No

The name of a single <wdk:link> widget. If the user clicks this widget,
the promptForSave does not interfere. The link functions normally.

safeAction

There may be as many safeAction tags as you like on a model page.

No
check

The name of an "input" widget (such as a <wdk:textarea>,


<wdk:list>, and so on.) If the user clicks on a link that is not listed
as a "safe action", the page examines every widget that is marked as
check. If the user has changed any of these widgets from the initial
state, the user is asked if he or she intends to discard changes.

| WDK Widgets | 209

Child Element

Required

Value
Note: While this element is not required, the promptForSave
widget has no effect if there is not at least one <check> tag.

No
prompt

Message to display in the dialog box asking if the user intends to discard
changes. The dialog box contains "OK" and "Cancel" buttons. Clicking
"OK" causes the link to function normally, discarding changes. Clicking
"Cancel" leaves the user on the same page, without discarding changes.
You should phrase the prompt appropriately. (That is, "Changes will be
lost! Is that okay?" would be appropriate, but "Do you want to save
changes?" would be wrong, since clicking "OK" discards changes.)
If this tag is not present, a default prompt is used.

As an example, suppose that a particular model page had several links, two of which had the names "save" and
"cancel", and had input widgets named "user_name" and "start_date". You might define a promptForSave
widget like this:

<wdk:promptForSave name="pfs">

<safeAction>save</safeAction>

<!-- User can click "Save" or -->

<safeAction>cancel</safeAction> <!-- "Cancel", and this widget does

not interfere -->

<check>user_name</check> <!-- Monitor these two widgets -->

<check>start_date</check>

<prompt>Do you want to leave without saving your changes?</prompt>

</wdk:promptForSave>
If the user clicks any link besides the ones named "save" and "cancel", the page examines the "user_name" and
"start_date" widgets. If the user had changed either of them from its initial value, the page displays a dialog button
saying, "Do you want to leave without saving your changes?" If the user clicks "OK", the link functions normally
(presumably losing the user's changes). If the user clicks "Cancel", the link has no effect, and the user stays on the first
page.

| WDK Widgets | 210

<wdk:sabaPicker>
The <wdk:sabaPicker> can be used to place a picker (a combination of a text field, a link, and a popup window
displaying a finder) on a page. That is, the page containing a sabaPicker displays a text field and a link. When the
user activates the link, the popup window containing the finder is launched. The following screen shows how the widget
is displayed, and the popup window it creates when clicked.

Figure 4-3:) User Interface of the <wdk:sabaPicker> Widget


The widget creates two form elements: a text field for displaying the selection, and a hidden field for storing the ID of
the selection. The name of the hidden field is specified by the text in the widget's <id> child tag. If there is no <id>
tag, the widget's name attribute is used. The name of the text field form element is computed by appending the value
of PickerWidget.kTextFieldExtension to the name of the hidden field.
The <wdk:sabaPicker> tag has one attribute:
Table 4-83:)Attributes of <wdk:sabaPicker> Tag
Attribute Name

name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:

| WDK Widgets | 211

Table 4-84:)Child Elements of <wdk:sabaPicker> Tag


Child Element

Required

Value

No

The name of a JavaScript function to launch when the user clicks


the Done link in the popup window. The function must have the
signature:

callbackFn

function(id, ui, targetAttribute)


The widget does not examine any return value from the function.

No

If true, users can type directly in the text box instead of launching
the popup window. Defaults to false.

No

The ID of the finder to display in the popup window. Either this


element or finderObject must be present.

No

The fully-qualified class name of the object to find. (The appropriate


finder is used based on the class specified.) Either this element or
finderId must be present.

No

This id is the name of the generated element. If not specified, the


value of the widget's name attribute is used.

No

Variables to submit to the page loaded when this link is clicked.


There may be zero, one, or more input tags. (The format is the
same as for the <field> child element of <wdk:link>.)

No

The label to display next to the element.

Yes

Header text to display in the popup window.

enable

finderId

finderObject

id

input

label

pageHeader

No
pickerSearchAttribute

No
prompt

Text prompt to show (for example, with a ToolTip) when the


pointer hovers over the link.

| WDK Widgets | 212

Child Element

Required

Value

No

If true, the widget is displayed in a way indicating that its value


is required (for example, the label might be displayed in red). De~
faults to false.

No

Size of the text field.

No

Initial value of the (read-only) text field.

No

If "true", a colon is appended to every label in the list (for example,


if the label is "Taxable", it is displayed as "Taxable:"). Defaults
to "false".

No

Initial value of the hidden field. Defaults to the empty string.

No

If "true", the widget is displayed as text, and is not editable. Default


is "false", unless there is a model object (as described in "Widgets
and Model Objects").

No

Specifies the picker's width. Possible values are default (normal


picker width) or wide.

No

Specifies the height of the picker's popup window. Height is specified


in number of pixels. The value should be a number between 300
and 600 (inclusive). Defaults to 300.

No

Tag used to attach the field to a Node in the <wdk:model>. This


is described further in "<wdktags:attachTo>""<wdktags:attachTo>"
on page 3-24.

required

size

textValue

useLabelTerminator

value

viewOnly

widthType

height

wdktags:attachTo

<wdk:saveLink>
This widget generates a "save" link. It behaves exactly like <wdk:frameworkLink> (described on ), with the following
exceptions:

If <label> is not specified, the default Save label is used.


If <prompt> is not specified, the default Save prompt is used.

| WDK Widgets | 213

If there is no <field> or <do> child, Saba automatically inserts

<field>

<name>actionKey</name>

<value>update</value>

</field>
The <wdk:saveLink> widget has all the same attributes and subelements as <wdk:frameworkLink>.

<wdk:script>
This widget inserts a JavaScript expression or function call into the application page.
The <wdk:expression> tag has one attribute:
Table 4-85:)Attributes of <wdk:expression> Tag
Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:


Table 4-86:)Child Elements of <wdk:expression> Tag
Child Element

Required

Value

Yes

Indicates whether the widget is an expression or a function call.

type

If type is set to the literal value expression, the widget's first


<arg> is inserted in the application page as a JavaScript expression.
If type is anything else, it is understood to be a function name. The
application page contains a call to the JavaScript expression with that
name, passing the parameters specified by the arg nodes.

Yes
arg

If the widget is of type expression, the firstarg node's contents are


inserted as a JavaScript expression.
If the widget is a function call, the contents of the <arg> nodes are
passed as parameters, in the order they are listed.

| WDK Widgets | 214

Child Element

Required

Value
Each <arg> node must have a type parameter. Currently, the only
supported type is String.

| WDK Widgets | 215

Example: Function Call


This example illustrates how to use a widget to call the JavaScript alert function. The following code in the model
file defines a script widget:

<wdk:script name="selectItemAlert">

<type>alert</type> <!-- call the function named 'alert' -->

<arg type="String"> <!-- Pass this argument to function -->

<xsp:expr>

<wdktags:textValue modelSource="true"

path="selectItemAlert"/>

</xsp:expr>

</arg>

</wdk:script>
You could then call the function by including the widget in the view file, for example like this:

if (noSelect == "true")

<xsl:apply-templates

select="$wdkWidget[@name='selectItemAlert']"/>;

return false;

}
This is equivalent to using the following JavaScript code:

| WDK Widgets | 216

if (noSelect == "true")

alert('Whatever the alert label is...');

return false;

}
Example: JavaScript Expression
You can also use the widget to insert a JavaScript expression. For example, you might include the following widget in
the model file:

<wdk:script name="chooseOperatorAlertMsg">

<type>expression</type> <!-- Insert an expression -->

<arg type="String"> <!-- Expression to insert -->

<wdktags:i18n.label name="kI18n20312ChooseOperatorAlert" bundle="platform"/>

| WDK Widgets | 217

</arg>

<arg>condLabel</arg>

</wdk:script>
You could then insert the string by including the widget in the view file, for example like this:

if ( someCondition == true)

var chooseOperatorAle

rtMsg =

<xsl:apply-templates

select="$wdkWidget[@name='chooseOperatorAlertMsg']"/>;
}
This expression is evaluated as

if ( someCondition == true)

var chooseOperatorAlertMsg = 'Some msg';

<wdk:table>
Used to display information in tabular form. The table can contain information or widgets.

| WDK Widgets | 218

The <wdk:table> tag has one attribute:


Table 4-87:)Attributes of <wdk:table> Tag
Attribute Name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

name

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:


Table 4-88:)Child Elements of <wdk:table> Tag
Child Element

Required

Value

No

The text to display if the table contains no elements. If this tag is not
present, an empty table displays the text defined by the common_la~
bels resource bundle's kI18n5006EmptyTableMessage tag.

No

Indicates whether to show the emptyTableText if the table has no


rows. Default value is true (show empty table text). You may want to

emptyTableText

showEmptyTableText

| WDK Widgets | 219

Child Element

Required

Value
set it to false if, for example, the table is used to display search results
and the search has not yet been executed.

No

If true, provide an Export link to let users export table to CSV format.
If false, do not provide this link. Defaults to true.

No

Defines the heading row. The format of this tag is described in "<head>
Child Element".

No

If present, creates an Add button letting users add objects of the type
shown by the finder, and specifies what page should be launched by the
Add button. For details about the onAdd widget's contents, see below.

No

If true (the default), all rows are displayed on one page. If false,
the table is split into multiple pages, with each one having the number
of rows specified by the <pageSize> attribute.

No

Number of rows to display on each page. Ignored unless <disablePa~


ging> is set to false.

No

If true, provide an Print link to let users open the table in a new win~
dow, formatted for printing. If false, do not provide this link. Defaults
to true.

Yes

Defines the rows of the table. The row is attached to a particular node
path in the model. It is repeated once for each matching node. The format
of this tag is described in "<row> Child Element".

No

Tag used to attach the field to a Node in the <wdk:model>. This is


described further in "<wdktags:attachTo>""<wdktags:attachTo>" on
page 3-24.

export

head

onAdd

disablePaging

pageSize

print

row

wdktags:attachTo

Note: The table rows do not necessarily draw their information


from this node! The <row> attribute defines its own path relative
to the node attached to, and elements in the row draw information
from the node specified by the row path.

No
width

The width of the table. May either be a number, specifying the width in
pixels (for example, "600"), or a percentage, specifying the width as

| WDK Widgets | 220

Child Element

Required

Value
a percentage of the browser's width (for example, "75%"). If not spe~
cified, the browser sizes the table automatically.

No

Allows you to customize the style of the table header. (This is particularly
useful for portlets.) The format of this tag is described in "<headerStyle>
Child Elements".

No

This node contains any special links you want to add to the top of the
widget. The <actionlinks> node contains one or more <data>
node, each of which specifies a link. For more information about
<data> nodes, see "<data>".

No

If set to true, the tree renders the top header in such a way as to align
to a tab. This means that the display area's corners is not rounded.

headerStyle

actionLinks

tabView

The default value is false.

No

The label displayed at the left corner of the table.

No

Default value is alse. If set to true, a multi-page table does not display
page numbers. Instead, each page displays a Previous and/or Next link
as appropriate.

No

If true (the default), the table has a Modify Table link.

No

Specifies the page to load when the Modify Table popup window is
saved and closed, or when the user clicks a Previous or Next link, a
page number, or a sortable column header. Must have one and only one
of the following child elements:

title

disablePageNumber

modifyTable

onAction

<href>: URL of web page to open.


<mainPage>: Saba application page to open.
In addition, may have a <field> child, specifying which field to
pass to the next page.

No
resetPosition

Set to true to indicate page position should be reset to beginning. De~


fault is false.

| WDK Widgets | 221

<head> Child Element


The <head> tag defines the table's heading row, if any. There does not have to be a heading row. However, if there is,
it should have one column tag for each column defined by the <row> tag.
The <head> tag has no attributes. It has one child element:
Table 4-89:)Child Elements of <wdk:table><head> Tag
Child Element

Required

Value

Yes

The heading for the column. The column headings are displayed in the
order they are defined. Column headings may contain text or widgets.

column

The <column> elements define the headers for each column. These elements have two optional attributes:
Table 4-90:)Attributes of <wdk:table><head><column> Tag
Child Element

Required

Value

No

Width of the column, either expressed as several pixels (for example,


width="80") or as a percentage of the table's width (for example,
width="25%").

No

Describes the contents of the column. The default value is "text".


Valid values include:

width

type

"icon": the column contains an icon (either a link or other icon).


The cell should be middle aligned. This column is not printed or
exported.
"numeric": The column contains numeric data. The cell should
be right aligned.
"text": The column contains text data. The cell should be left
aligned.
"checkbox": The column contains checkboxes. The cell should
be left aligned. The value exported/printed is 'Y' for a checked
checkbox and an 'N' for an unchecked checkbox. When the type of
head column is specified as "checkbox", the row column must have
a specify value tag, which indicates what value is being submitted.
"radio": The column contains radio buttons. The cell should be
left aligned. The value exported/printed is 'Y' for the selected button
and an 'N' for all unselected buttons. When the type of head column
is specified as "radio", the row column must have a specify value
tag, which indicates what value is being submitted.
"generic": Cell contains miscellaneous data.

If the column is of type checkbox, it can have the following child elements:

| WDK Widgets | 222

Table 4-91:)Child Elements of <wdk:table><head><columnb> Tag


Child Element

Required

Value

No

If true, display a Check All box in the column header. Users can click
that box to check or uncheck all columns. Default is false.

Yes

Checkbox name to use when submitting. This must contain a <prefix>


node specifying the prefix of the node's name. It may optionally contain
a <postfix> specifying a postfix for the ID.

Yes

Checkbox value to use when submitting.

No

If checkAll is false, the label is used as the column's header. (If


checkAll is true, this node is ignored. There is a checkbox in the
header that the user can check to select the entire column.)

checkAll

id

value

label

<row> Child Element


The <row> tag is used to set which information is displayed in the table. The row tag is attached to a particular node
path in the <wdk:model> section (not necessarily the node the table as a whole is attached to!). The row is repeated
once for each matching node. The column contents are drawn from that node's contents. For example, the model section
might contain several <person> nodes, each containing a <first_name>, <last_name>, <dept>, and so on.
The <row> tag is attached to the <person> node. This gives the table one row for each<person> node in the model.
Each row could display the first name, last name, and so on., of the corresponding person.
The <row> tag has one attribute:
Table 4-92:)Attributes of <wdk:table><row> Tag
Attribute Name

Required

Value

Yes

An XPath expression, relative to the node the table is attached to, specifying
the root node for rows. There is one row in the table for each corresponding
node.

path

The tag has the following child element:


Table 4-93:)Child Elements of <wdk:table><row> Tag
Child Element

column

Required

Value

Yes

Contents of the column. This may contain text or widgets. Any <wdk~
tags:nodeRef><wdktags:nodeRef> tags are specified relative to the node
that this row is attached to (not the node that the table, as a whole, is
attached to). Of course, the <wdktags:nodeRef> can override this
path by defining a source attribute.

| WDK Widgets | 223

<onAdd> Child Element


If the <onAdd> child element is present, the results screen has an Add button. The element specifies which page is
opened when the user clicks Add. This node follows the syntax of <wdk:link> or <wdk:frameworkLink>. In addition,
it can have the following child elements:
Table 4-94:)Child Elements of <wdk:finderResult><onAdd> Tag
Child Element

Required

Value

No

If specified (and no label tag is specified) then the display name of the
component is used to autogenerate the label of the link.

No

Indicates what type of action is used to create the object. This is used
in combination with component to generate the label for the button,
if ?the onAdd does not have label or prompt tags. Appropriate
values are create (default) or add. If a label isn't specified, the ap~
plication generates an appropriate label from this and the component's
display name (for example, "Add Course", "Create Goal").

component

actionType

<headerStyle> Child Elements


If you wish, you can specify the style settings for the tree widget's header row by including a <headerStyle> node.
This node has children specifying the style names to apply to the various header elements:

| WDK Widgets | 224

Table 4-95:)Child Elements of <wdk:table><headerStyle> Tag


Child Element

Required

Value

No

Style for cell ends.

No

Style for tree columns that contain content (that is, not the first
and last columns, which provide the rounded border for the table
containing the tree).

No

Style of text in the header row.

No

Style of text for the header of whichever column is used to sort


the tree.

No

Style to apply to the end column if it is used to sort the tree.

No

Style to apply to content column if it is used to sort the tree.

endStyle

contentStyle

textStyle

sortLinkStyle

sortEndStyle

sortContentStyle

| WDK Widgets | 225

Example
Suppose a particular model page contained a section describing students, along these lines:

<wdk:model>

<students>

<student>

<name>Neville Longbottom</name>

<id>nlongbot</id>

<house>Gryffendor</house>

</student>

<student>

<name>Cho Chang</name>

<id>cchang</id>

<house>Ravenclaw</house>

</student>

<!-- ...other student nodes... -->

</students>

<!-- ...other model contents... -->

</wdk:model>

| WDK Widgets | 226

A table of students might be set up like this:

<wdk:table name="Students">

<wdktags:attachTo p

ath="studen t

"/><!-- Attach table to this

node... -->

<head>
<column>Name</column>

<column>House</column>

</head>

<row path="student"> <!-- Each *row* in the table is attached

to a <student> subnode.-->

<column><!-- First column has the name, and a link widget

to let people edit that name. -->

<wdk:link name="editLink">

<href>EditStudent.xml</href>

<!-- Control file maps this to a link to a control


page... -->

<label>

<wdktags:nodeRef path="name"/>

| WDK Widgets | 227

<!-- Fetch the <name> child node of this

particular student-->

</label>

<field>

<name>id</name>

<value>

<wdktags:nodeRef path="id"/>

<!-- This student's ID is passed as the

value of the link. -->

</value>

</field>

</wdk:link>

</column>

<column>

<wdktags:nodeRef path="house"/>

</column>

</row>

| WDK Widgets | 228

</wdk:table>
The table ends up looking something like this:
Name

House

Neville Longbottom

Gryffendor

Cho Chang

Ravenclaw

Draco Malfoy

Slitherin

Parvati Patil

Gryffendor

The exact format of the table is determined by the site's stylesheets. Each user's name is a hyperlink. If the user clicked
on a user's name, the EditStudent page is launched and is passed an id HTML parameter, whose value would be
that particular student's user ID.
Note that the column headings could also be widgets. For example, they might be set up so the user could sort the table
by clicking on a column's heading.

<wdk:textarea>
The <wdk:textarea> widget creates a field for multi-line text entry. (The standard widget library transforms it into
an HTML <textarea> tag.)
The <wdk:textArea> tag has one attribute:
Table 4-96:)Attributes of <wdk:textarea> Tag
Attribute Name

name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:

| WDK Widgets | 229

Table 4-97:)Child Elements of <wdk:textarea> Tag


Child Element

Required

Value

No

This id is the name of the generated element. If not specified, the value
of the widget's name attribute is used.

No

Tag used to attach the field to a Node in the <wdk:model>. This is


described further in "<wdktags:attachTo>""<wdktags:attachTo>" on
page 3-24.

No

The label to display next to the element.

No

If true, the widget is displayed in a way indicating that its value is re~
quired (for example, the label might be displayed in red). Defaults to
false.

No

The width of the widget in characters.

No

Height of the widget in lines.

No

Initial text when the text area is first shown.

No

If "true", the widget is displayed as text, and is not editable. Default


is "false", unless there is a model object (as described in "Widgets
and Model Objects").

No

If "true", a colon is appended to the label. (That is, if the label is


"Notes", it is displayed as "Notes:".) Default value is "false".

id

wdktags:attachTo

label

required

width

height

value

viewOnly

useLabelTerminator

| WDK Widgets | 230

The following example illustrates the use of the <wdk:textarea> widget:

<wdk:textarea name="abstract">

<wdktags:attachTo path=""/> <!-- Attach to the <wdk:model> node -->

<value>Here is a very short abstract.</value>

<width>80</width>

<height>10</height>

<required>true</required>

<label>Abstract</label>

</wdk:textarea>

<wdk:timeInput>
The wdk:timeInput widget is used for inputting a time (hour and minute). The time value should be in the time format
returned by the com.saba.util.DateConvertUtil.getTimeFormatter(ServiceLocator) method.
The <wdk:timeInput> tag has one attribute:
Table 4-98:)Attributes of <wdk:timeInput> Tag
Attribute Name

name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:

| WDK Widgets | 231

Table 4-99:)Child Elements of <wdk:timeInput> Tag


Child Element

Required

Value

No

This id is the name of the generated element. If not specified, the value
of the widget's name attribute is used.

No

Tag used to attach the field to a Node in the <wdk:model>. This is


described further in "<wdktags:attachTo>""<wdktags:attachTo>" on
page 3-24.

No

The label to display next to the element.

No

Initial value (in an appropriately-formatted string), if any.

No

If true, the widget is displayed in a way indicating that its value is re~
quired (for example, the label might be displayed in red). Defaults to
false.

No

If "true", the widget is displayed as text, and is not editable. Default


is "false", unless there is a model object (as described in "Widgets
and Model Objects").

No

If "true", a colon is appended to the label. (That is, if the label is


"Start time", it is displayed as "Start time:".) Default value
is "false".

id

wdktags:attachTo

label

value

required

viewOnly

useLabelTerminator

| WDK Widgets | 232

Here is an example of a timeInput widget:

<wdk:timeInput name="startTime">

<wdktags:attachTo path=""/>

<value>05:23</value> <!-- Initial value -->

<label>Start Time</label>

</wdk:timeInput>

<wdk:tree>
This widget displays hierarchical data in tree form. It has one attribute:
Table 4-100:)Attributes of <wdk:tree> Tag
Attribute Name

name

Required

Value

Yes

The name of the widget. The view stylesheet uses this name to identify the
widget. For example,

<xsl:apply-templates select="wdk:widget[@name='your~
WidgetName']"/>

The widget has the following child elements:

| WDK Widgets | 233

Table 4-101:)Child Elements of <wdk:tree> Tag


Child Element

Required

Value

Yes

Java expression that evaluates to an object implementing the


ITreeModel interface. This object specifies the contents of the
tree.

No

Used for the title of the tree. The title appears in the upper left
hand corner of the widget in the standard typeface for table and
finder titles.

No

If set to true, the tree renders the top header in such a way as to
align to a tab. This means that the display area's corners is not
rounded.

treeExpr

treeTitle

tabView

The default value is false.

No

This node contains any special links you want to add to the top of
the widget. The <actionlinks> node contains one or more
<data> node, each of which specifies a link. For more informa~
tion about <data> nodes, see "<data>".

No

Allows you to specify custom style features for the tree's header.
This node is described fully in "<headerStyle> Child Elements".

No

Text to show if the tree is empty (that is, does not have a root node
or any children).

No

Title text for the header of the main column.

Yes

Specifies the way the tree should be arranged. There are two sup~
ported values:

actionLinks

headerStyle

emptyText

mainColumnHeader

layoutFlavor

No
mainPage

genericTree: A standard tree. Nodes can expand to any


depth.
oneLevelTree: Tree that displays only one level (a parent
and its children) at a time. If user expands a node, that node
and its children is displayed, along with a link to the parent
node.

Saba application page that this tree is embedded in.

| WDK Widgets | 234

Child Element

Required

Value
Note: Either <mainPage> or <href> must be present.

No
href

Page that this widget is embedded in, specified as a URL. If


present, indicates that the tree's page is not part of the Saba
framework.
Note: Either <mainPage> or <href> must be present.

No

If true, the widget initially displays with the root node open and
the root's child elements visible. If false, the widget initially
displays with the root node closed and no other nodes visible.
Defaults to false.

No

If false, do not display the root node of the tree. Default value
is true.

No

If true, the widget initially displays with all nodes open (overrid~
ing any setting for <rootNodeOpen>). If false, the widget
initially displays either the root node or the root node and its im~
mediate children, depending on the setting of <rootNodeOpen>.
Defaults to false.

No

Image to use as a connector when drawing nodes. If not present,


default graphic is used.

No

Image to use as the connector to the last node in the tree. If not
present, default graphic is used.

Yes

Each tree may have one or more node types. Each node in the tree
has one of these types. The node type specifies how the node is
displayed.

rootNodeOpen

displayRootNode

allNodesOpen

middleNodeConnectorGIF

lastNodeConnectorGIF

nodeType

| WDK Widgets | 235

Child Element

Required

Value
This element is described further in "<nodeType> Child Elements".

Yes

Specifies what happens when the user opens or closes a node. This
must contain a single <data> element, specifying the link to
open. The <data> element is described further in "<data>".

No

Specifies the maximum number of nodes the tree can display. The
default value is 100. If there are more nodes than this, the tree
displays only maxNodes.

No

Text to display if the tree was truncated because it had more data
than the maxNodes limit.

No

If this node is present, the tree automatically spaces widgets that


are placed in the tree's column.

No

Widgets and other tags that is displayed immediately to the left


of each node. This must contain a single <data> element, spe~
cifying the data or widgets to display. The <data> element is
described further in "<data>".

No

Widgets and other tags that is displayed immediately to the right


of each node. This must contain a single <data> element, spe~
cifying the data or widgets to display. The <data> element is
described further in "<data>".

No

Specifies whether the tree should show a Modify Table link, al~
lowing the user to modify the tree's data. Default is true.

No

Specifies the CSS style to use for the tree's background.

No

Specifies the page to load when the Modify Table popup window
is saved and closed.

No

Specifies the tree widget's header. This is described further in


"<head> Child Elements".

openCloseWidget

maxNodes

nodeLimitReachedText

enableWidgetSpacing

leftWidgets

rightWidgets

modifyTable

backGroundStyle

onAction

head

| WDK Widgets | 236

<head> Child Elements


If you wish, you can specify the contents of the header row by including a <head> node:
Table 4-102:)Child Elements of <wdk:tree><head> Tag
Child Element

Required

Value

No

If present, the header column is a link to this URL.

No

If present, the header column is a link to this application page.

No

Contents of the header column. This may contain text or widgets.


Any <wdktags:nodeRef><wdktags:nodeRef> tags are specified
relative to the node that this row is attached to (not the node that
the table, as a whole, is attached to). Of course, the <wdk~
tags:nodeRef> can override this path by defining a source
attribute. The column node has a single child, <label>, contain~
ing the column's label. It can also have two attributes, which are
used for sorting the tree:

href

mainPage

column

@nodeProperty: Identifies which part of each tree data


node contains the data used for sorting.
@dataType: Specifies the type of data used for sorting. This
can be string, date, time, or number.

<headerStyle> Child Elements


If you wish, you can specify the style settings for the tree widget's header row by including a <headerStyle> node.
This node has children specifying the style names to apply to the various header elements:

| WDK Widgets | 237

Table 4-103:)Child Elements of <wdk:tree><headerStyle> Tag


Child Element

Required

Value

No

Style for cell ends.

No

Style for tree columns that contain content (that is, not the first
and last columns, which provide the rounded border for the table
containing the tree).

No

Style of text in the header row.

No

Style of text for the header of whichever column is used to sort


the tree.

No

Style to apply to the end column if it is used to sort the tree.

No

Style to apply to content column if it is used to sort the tree.

endStyle

contentStyle

textStyle

sortLinkStyle

sortEndStyle

sortContentStyle

<nodeType> Child Elements


The tree must define at least one <NodeType> (but may have as many as it you like). The node type specifies how
nodes of that type should be displayed. It has the following subelements:
Table 4-104:)Child Elements of <wdk:tree><nodeType> Tag
Child Element

nodeTypeName

Required

Value

Yes

Unique name for this node type. The ITreeModel interface has
a method getNodeType, which returns the type of a node. The

| WDK Widgets | 238

Child Element

Required

Value
return value must match the <nodeTypeName> for one of the
tree's <nodeType> elements.

Yes

Data to display for elements of this type. For further information


about the <data> node, see "<data>".

No

Image to use if this node is closed. If not present, default graphic


is used.

No

Image to use if this node is open. If not present, default graphic is


used.

No

CSS style to apply to the node's background.

data

closedNodeGIF

openNodeGIF

backGroundStyle

| WDK Widgets | 239

Example 1: A Simple Tree


The following fragment defines a tree widget that displays a link for each node. Everything else uses the default values
provided. This tree declaration uses the <wdk:nodeProperty> tag to return a property of the current node:

<wdk:tree name="wdkTreeWidget">

<treeExpr>tree</treeExpr>

<mainPage>viewTree.xml</mainPage>

<displayRootNode>true</displayRootNode>

<middleNodeConnectorGIF>

<xsp:expr>wdkSite.getImageRoot()</xsp:expr>/i_expand.gif

</middleNodeConnectorGIF>

<lastNodeConnectorGIF>

<xsp:expr>wdkSite.getImageRoot()</xsp:expr>/i_expand_last.gif

</lastNodeConnectorGIF>

<nodeType>

<nodeTypeName>personNode</nodeTypeName>

<openNodeGIF>

<xsp:expr>wdkSite.getImageRoot()</xsp:expr>/i_minus.gif

</openNodeGIF>

| WDK Widgets | 240

<closedNodeGIF>

<xsp:expr>wdkSite.getImageRoot()</xsp:expr>/i_plus.gif

</closedNodeGIF>

<data>

<wdk:frameworkLink name="abc">

<label><wdk:nodeProperty><name>displayName</name></wdk:nodeProperty></label>

<prompt><wdk:nodeProperty><name>displayName</name></wdk:nodeProperty></prompt>

<href>viewTree.xml</href>

<mainPage><xsp:expr>onLeafMainPage</xsp:expr></mainPage>

<field>

<name><xsp:expr>OrgTree.kLeafHandler</xsp:expr></name>

<value><wdk:nodeProperty><name>id</name></wdk:nodeProperty></value>

</field>

</wdk:frameworkLink>

</data>

</nodeType>

</wdk:tree>

| WDK Widgets | 241

Example 2: A Complex Tree


This example uses all of the tree widget's functionality. It does this by defining its own classes to implement the
ITreeModel and ITreeModelNode interfaces.

| WDK Widgets | 242

The widget uses an ITreeModel class with the following definition:

public class AudienceTypeTreeModel implements ITreeModel {

private ITreeModelNode mRootNode = null;

private ServiceLocator mServiceLocator;

/** Creates new AudienceTypeTreeModel */

public AudienceTypeTreeModel( AudienceType type, ServiceLocator

locator )

throws SabaException {

mRootNode = new AudienceTypeModelNode( type,

null, locator );

mServiceLocator = locator;

/**

* Nodes have types, in the simplest case where the user

* does not care about this type

* then a constant value can be returned. Used in

* conjuction with the TreeWidget (wdk:widget) as well.

| WDK Widgets | 243

* @return int node type.

*/

public String getNodeType(ITreeModelNode node) {

String type = node.getProperty(

AudienceTypeModelNode.kNodeType );

return type;

/**

* @return empty ITreeWidgetNode[] if leaf node, otherwise

* return all direct first level children of this node.

In the example,

getChildren( "A" )

returns ("B", "D")

*/

public ITreeModelNode[] getChildren(ITreeModelNode node)

throws SabaException {

| WDK Widgets | 244

return

((AudienceTypeModelNode)node).getChildren();

/**

* Checks if the node is a root node. See class

* description to see why this

* method is necessary. It is advised to call this method

* before calling

* certain operations in this interface.

* @param node the ITreeModelNode we are testing for root.

* @return true if node is a root node, false otherwise.

*/

public boolean isRootNode(ITreeModelNode node) throws

SabaException {

return node == mRootNode;

| WDK Widgets | 245

/**

* Finds a node given an id of a node. This id was

* obtained from ITreeModelNode.getId()

* @param id the String id for a node.

*/

public ITreeModelNode findNodeById(String id) throws

SabaException {

return null;

/**

* Checks if this tree should still be kept in the cache.

*/

public boolean isValid() {

return true;

/**

| WDK Widgets | 246

* Gets the parent of a node.

* @return a Parent of this node or null if no

* parent. Only root node returns null.

*/

public ITreeModelNode getParent(ITreeModelNode node)

throws SabaException {

return

((AudienceTypeModelNode)node).getParent();

/**

* Gets the root node for this particular instance of the

* tree. The root node

* is the ancestor of all other nodes in the tree.

* @return root node. In the example above, returns "A".

*/

public ITreeModelNode getRootNode() throws SabaException {

| WDK Widgets | 247

return mRootNode;

}
It uses a single ITreeModelNode class with the following definition. (Note that while this example uses a single
class for all nodes, you are free to use multiple node classes, using a different class for each node type, if you wish.)

public class AudienceTypeModelNode implements ITreeModelNode{


private
AudienceType mAudType = null;
private SeatCategory mSeatCat = null;
private String mId = null;
private String mNodeType = null;
private
String mDisplayName = null;
private HashMap mProps = new HashMap();
private ITreeModelNode mParent;
private ServiceLocator mServiceLocator;
private static final String kAudType
= "audiencetype";
private static final String kSeatCatType
= "seatcat";
public static final String kId
=
"id";
public static final String kDisplayName
= "displayName";
public static final String kNodeType
= "nodeType";
/** Creates new AudienceTypeModelNode */
public AudienceTypeModelNode(
AudienceType type, ITreeModelNode parent, ServiceLocator locator ) throws
SabaException
{ mAudType = type; mId = type.getId(); mDisplayName =
type.getDisplayName(); mNodeType = kAudType; mParent = parent; mServiceLocator
= locator; addProperties();
}
public AudienceTypeModelNode( SeatCategory
category, ITreeModelNode parent, ServiceLocator locator ) throws SabaException
{ mSeatCat = category; mId = category.getId(); mDisplayName =
category.getDisplayName(); mNodeType = kSeatCatType; mParent = parent;
mServiceLocator = locator; addProperties();
}
/**
* Get a unique key
for this node within this tree. Two nodes within a tree are determined to be
the
* same if they have matching uniqueNodes.
*
* @return a unique
String identifier for this node in the tree it belongs in.
*/
public
String getUniqueNodeId() { return mId;
}
/**
* Each node supports
properties that are only understood by the implemetor of this interface.
* The propertyNames can be any values.
*
* @param propertyName
the String property name that we are looking for.
* @return the Value
of the property found, if not found, then return null.
*/
public String
getProperty(String propertyName)
{ return getProperty( propertyName, null
);
}
/**
* Each node supports properties that are only understood
by the implemetor of this interface.
* The propertyNames can be any values.
*
* @param propertyName
the String property name that we
are looking for.
* @param defaultValue the String defaultValue returned
if the property was not found.
* @return the Value of the property found,
if not found, then return the default value.
*/
public String
getProperty(String propertyName, String defaultValue) { String prop =
(String)mProps.get( propertyName ); return ( (prop == null) ? defaultValue :
prop );
}
ITreeModelNode getParent()
{ return mParent;
}
/**
* Get them each time as they may change.
*/
ITreeModelNode[]
getChildren() throws SabaException
{ Collection subjects =
Collections.EMPTY_LIST; AudienceTypeManager auMgr =
(AudienceTypeManager)mServiceLocator.getManager( Delegates.kAudienceTypeManager
); if ( mAudType != null )
subjects =
auMgr.findDirectSeatCategories(mAudType); else if ( mSeatCat != null )
subjects = auMgr.findDirectSeatCategories(mSeatCat); int size =
subjects.size(); ITreeModelNode[] nodes = new ITreeModelNode[size]; SeatCategory
cat; int j = 0; for ( Iterator i = subjects.iterator(); j < size &&
i.hasNext(); )
{ cat = (SeatCategory)i.next(); nodes[ j++ ] = new
AudienceTypeModelNode( cat, this, mServiceLocator );
} return nodes;
}

| WDK Widgets | 248

private void addProperties()


{ mProps.put( kId, mId ); mProps.put(
kDisplayName, mDisplayName ); mProps.put( kNodeType, mNodeType );
}}
Finally, the model page contains this XML code to specify the tree widget itself:

Session s = SessionManager.getSession( sessionKey );

AudienceTypeTreeModel tree =
(AudienceTypeTreeModel)s.getAttribute("audTree");

if ( tree == null )

tree = new AudienceTypeTreeModel( audType,


wdkServiceLocator );

s.setAttribute( "audTree", tree );

<wdk:tree name="wdkTreeWidget">

<treeExpr>tree</treeExpr>

<layoutFlavour>genericTree</layoutFlavour> <!-genericTree (default),

oneLevelTree-->

<mainPage>browseEditAudType.xml</mainPage>

<displayRootNode>true</displayRootNode>

<!--

| WDK Widgets | 249

<middleNodeConnectorGIF><xsp:expr>wdkSite.getImageRoot()</xsp:expr>/i_expand.gif</mi

ddleNodeConnectorGIF>

<lastNodeConnectorGIF><xsp:expr>wdkSite.getImageRoot()</xsp:expr>/i_expand_last.gif</la

stNodeConnectorGIF>

-->

<leftWidgets>

<data>

<wdk:frameworkLink name="abc">

<icon><xsp:expr>wdkSite.getImageRoot() + "/" +

ImageConstants.kImgIconEdit</xsp:expr></icon>

<prompt><wdk:nodeProperty><name>displayName</name></wdk:nodeProperty></prompt>

<mainPage>/party/createNewAudienceType.saba</mainPage>

<input>

<name><xsp:expr>AudienceTypeBean.kAudienceTypeId</xsp:expr></name>

<value><wdk:nodeProperty><name>id</name></wdk:nodeProperty></value>

</input>

| WDK Widgets | 250

<input>

<name>audienceTypeName</name>

<value><wdk:nodeProperty><name>displayName</name></wdk:nodeProperty></value>

</input>

<input>

<name>audienceTypeDescription</name>

<value></value>

</input>

</wdk:frameworkLink>

</data>

</leftWidgets>

<rightWidgets>

<data>

<wdk:frameworkLink name="abc">

<icon><xsp:expr>wdkSite.getImageRoot() + "/" +

ImageConstants.kImgIconDelete</xsp:expr></icon>

| WDK Widgets | 251

<prompt><wdk:nodeProperty><name>displayName</name></wdk:nodeProperty></prompt>

<mainPage>browseEditAudType.xml</mainPage>

</wdk:frameworkLink>

</data>

</rightWidgets>

<!-- followed by one or more node types -->

<nodeType>

<nodeTypeName>audiencetype</nodeTypeName>

<openNodeGIF><xsp:expr>wdkSite.getImageRoot()</xsp:expr>/i_minus.gif</openNodeGIF>

<closedNodeGIF><xsp:expr>wdkSite.getImageRoot()</xsp:expr>/i_plus.gif</closedNodeGIF

>

<leftWidgets>

<data>

<wdk:frameworkLink name="abc">

<icon><xsp:expr>wdkSite.getImageRoot() + "/" +

ImageConstants.kImgIconEdit</xsp:expr></icon>

| WDK Widgets | 252

<prompt><wdk:nodeProperty><name>displayName</name></wdk:nodeProperty></prompt>

<mainPage>/party/createNewAudienceType.saba</mainPage>

<input>

<name><xsp:expr>AudienceTypeBean.kAudienceTypeId</xsp:expr></name>

<value><wdk:nodeProperty><name>id</name></wdk:nodeProperty></value>

</input>

<input>

<name>audienceTypeName</name>

<value><wdk:nodeProperty><name>displayName</name></wdk:nodeProperty></value>

</input>

<input>

<name>audienceTypeDescription</name>

<value></value>

</input>

</wdk:frameworkLink>

</data>

| WDK Widgets | 253

</leftWidgets>

<!--

<rightWidgets>

<data>

<wdk:frameworkLink name="abc">

<icon><xsp:expr>wdkSite.getImageRoot() + "/" +

ImageConstants.kImgIconDelete</xsp:expr></icon>

<prompt><wdk:nodeProperty><name>displayName</name></wdk:nodeProperty></prompt>

<mainPage>browseEditAudType.xml</mainPage>

</wdk:frameworkLink>

</data>

</rightWidgets>

-->

<data>

<wdk:frameworkLink name="abc">

<label><wdk:nodeProperty><name>displayName</name></wdk:nodeProperty></label>

<prompt><wdk:nodeProperty><name>displayName</name></wdk:nodeProperty></prompt>

| WDK Widgets | 254

<!--<style>sbSideTeamTextUnselected</style>-->

<mainPage>browseEditAudType.xml</mainPage>

</wdk:frameworkLink>

</data>

</nodeType>

<nodeType>

<nodeTypeName>seatcat</nodeTypeName>

<openNodeGIF><xsp:expr>wdkSite.getImageRoot()</xsp:expr>/i_minus.gif</openNodeGIF>

<closedNodeGIF><xsp:expr>wdkSite.getImageRoot()</xsp:expr>/i_plus.gif</closedNodeGIF

>

<leftWidgets>

<data>

<wdk:frameworkLink name="abc">

<icon><xsp:expr>wdkSite.getImageRoot() + "/" +

ImageConstants.kImgIconEdit</xsp:expr></icon>

<prompt><wdk:nodeProperty><name>displayName</name></wdk:nodeProperty></prompt>

| WDK Widgets | 255

<mainPage>/party/createNewAudienceType.saba</mainPage>

<input>

<name><xsp:expr>AudienceTypeBean.kAudienceTypeId</xsp:expr></name>

<value><wdk:nodeProperty><name>id</name></wdk:nodeProperty></value>

</input>

<input>

<name>audienceTypeName</name>

<value><wdk:nodeProperty><name>displayName</name></wdk:nodeProperty></value>

</input>

<input>

<name>audienceTypeDescription</name>
<value></value>

</input>

</wdk:frameworkLink>

</data>

</leftWidgets>

<rightWidgets>

| WDK Widgets | 256

<data>

<wdk:frameworkLink name="abc">

<icon><xsp:expr>wdkSite.getImageRoot() + "/" +

ImageConstants.kImgIconDelete</xsp:expr></icon>

<prompt><wdk:nodeProperty><name>displayName</name></wdk:nodeProperty></prompt>

<mainPage>browseEditAudType.xml</mainPage>

</wdk:frameworkLink>

</data>

</rightWidgets>

<data>

<wdk:frameworkLink name="abcd">

<label><wdk:nodeProperty><name>displayName</name></wdk:nodeProperty></label>

<prompt><wdk:nodeProperty><name>displayName</name></wdk:nodeProperty></prompt>

<!--<style>sbSideTeamTextSelected</style>-->

<style>sbListOddText1</style>

| WDK Widgets | 257

<mainPage>browseEditAudType.xml</mainPage>

</wdk:frameworkLink>

</data>

</nodeType>

</wdk:tree>

<wdk:validatorWidget>
Checks if the user has filled in all required information on the page's forms before submitting the information. When
you define the widget, you specify which <wdk:link> widgets it should modify. If the user clicks on any of those
links, the page verifies that the user has properly filled/selected all widgets that contain <required> child nodes. If
the user has not filled in any required widget (or selected an item from a required list, and so on.), or if a widget has
been filled in improperly (for example, text length exceeds the permitted length, or a numeric value is too low or too
high), the link does not function. Instead, the user is shown a dialog box describing the problem.
The validator judges a widget to be "required" if it contains a <required> child tag, or if the WDK engine can
determine (by examining the component dictionary) that the widget corresponds to a field that is required by the underlying
Saba object. In addition, if the component dictionary contains information about a widget's appropriate values (for
example, length of string, data type), the validator checks to see that the widget complies. For more information about
the component dictionary, see "Component Dictionary Support".
Unlike most widgets, the validator widget does not need to be included by the view page. Instead, the validator widget
modifies the behavior of links defined on the model page. If any of those links is used by a view page, the validator
widget functions-if the user clicks on the link, the page verifies that the forms are properly filled in before the link
functions.
The <wdk:validatorWidget> tag has one attribute:
Table 4-105:)Attributes of <wdk:validatorWidget> Tag
Attribute Name

Required

Value

No

The name of the widget. Since the widget is not placed on the page, naming it
is optional.

name

The widget has the following child elements:


Table 4-106:)Child Elements of <wdk:validatorWidget> Tag
Child Element

saveLink

Required

Value

Yes

The name attribute of a single<wdk:link> tag to monitor. If the user


clicks this link, the validator widget checks that the required fields are
properly filled in before the link functions.

| WDK Widgets | 258

Child Element

Required

Value
If you want the widget to monitor more than one link, put in multiple
<saveLink> tags.

No

The message to display if the user tries to submit the form without as~
signing values to all required fields. If <prompt> is not supplied, the
text from the common_labels resource's kI18n5006Required~
Prompt property is used.

No

The message to display if the text in a required field is longer than the
widget permits. If <maxLengthPrompt> is not supplied, the text
from the common_labels resource's kI18n5006TooLongPrompt
property is used.

No

The message to display if a required numeric widget's value is below


the widget's minimum value. If <minValuePrompt> is not supplied,
the text from the common_labels resource's
kI18n5006ValueTooSmallPrompt property is used.

No

The message to display if a required numeric widget's value is above


the widget's maximum value. If <maxValuePrompt> is not supplied,
the text from the common_labels resource's
kI18n5006ValueTooLargePrompt property is used.

No

The message to display if a widget value is not of the right data type
(for example, if a user enters alphabetic characters in a numeric field).
If <datatypePrompt> is not supplied, the text from the common_la~
bels resource's kI18n5006WrongDatatypePrompt property is
used.

prompt

maxLengthPrompt

minValuePrompt

maxValuePrompt

datatypePrompt

Component Dictionary Support


The component dictionary contains display-related information about Saba components. This information includes:
whether an attribute of a component is required, default values for components, component data types, and minimum
and maximum values.
The WDK engine supports using the component dictionary when processing a widget. The support includes the following:

XMLFacet support in ICommand.execute methods and IXMLObject.acceptXMLVisitor methods. The


XMLFacet concept lets these methods generate different XML renderings of the same business object without having
to change the Java code.
Each widget, if attached to an XML node representing an component attribute, can use the additional metadata in
the XML node (in the form of a special XML attribute) to paint the widget correctly. The special XML attribute
contains the name of the component and the name of the attribute the XML node represents.
Note: Currently the following information is pulled from the component dictionary: label, whether required,
maximum length, minimum and maximum value, datatype (real and integer types).

| WDK Widgets | 259

In order to take advantage of the component dictionary support in WDK, developers must do the following:
1. When generating the XML for a component, developers need to create the XMLFacet for the component.
2. Generate the XML for a component by using the visit(TagData ...) methods of the visitor.
3. Attach the widget to the XML representation of the component attribute.

Example
This example illustrates the various steps in adding component dictionary support to a component, and to the WDK
pages that use it. Some of these steps are followed by the developers who create Saba components (most of which are
shipped as part of the API). Others are followed by application page developer.
The example takes the case of a WDK page that is displaying information from a DomainDetail object. This object
is a Saba component that is integrated with the Component Dictionary.

| WDK Widgets | 260

DomainDetail's Support for the Component Dictionary


The DomainDetail object supports the XMLFacet model, as this code snippet shows:

public static final String kComponentName = "Domain";

public static final String kNameAttribute = "name";

public static final String kDescriptionAttribute = "description";

/**

* Accept a visitor. An implementation should ask the Visitor to visit each

* of its public elements (i.e., fields or properties).

* @param visitor The XML Visitor object

*/

public void acceptXMLVisitor(IXMLVisitor visitor) throws XMLVisitorException


{
XMLFacetType facetType = visitor.getFacetType();

//[1]

XMLFacet facet = facetType.create(kComponentName);

//[2]

visitor.visit(facet.getTagData(kNameAttribute), mName); //[3]

visitor.visit(facet.getTagData(kDescriptionAttribute),

mDescription);

//[4]

| WDK Widgets | 261

The italicized parts of the code support the XMLFacet model:


1. The visitor has a factory object that is used to create a facet object.
2. The facet is created with the component name as the argument. The component name must be the name of this
component in the component dictionary.
3. This form of the visit method takes a TagData object as an argument. In this example the
facet.getTagData() is called first with kNameAttribute (whose value corresponds to the attribute name
in the component dictionary), and then called with the kDescription attribute.
The result of these calls is the following XML fragment:

<name cdID="Domain:name">Domain1</name>

<description

cdID="Domain:description">Domain1 description</description>
The cdID attribute encodes minimal metadata about the component attribute represented by this node.

| WDK Widgets | 262

The Command Implementation


By and large, if a component object supports the component dictionary, the normal command implementation takes
advantage of it. For example, the following code could be used to generate the full XML for a domain detail object:

public void execute (Htt

pServletRequest req,

IXMLVisitor visitor,

Object argument) throws Exception

//OmainDetail passed in the hash map

DomainDetail obj = (DomainDetail)


((HashMap)argument).get("domai

nId");

visitor.visit(null, tag, obj, null);

}
The Model Page
The model page has to generate a widget that takes advantage of the component.

| WDK Widgets | 263

The <wdk:model> section contains code that generates the XML version of the component:

<wdktags:execute

manager="com.saba.client.domain.DomainCommandManager"

command="getDomainAndParents" return="model">

<param name="domainId" expr="theDomain" type="String"

mode="in"/>

</wdktags:execute>
The <wdk:widgets> section contains input widgets that map to the DomainDetail fields:

<wdk:input name="Name">

<wdktags:attachTo path="/domain/name" root="model"/>

<id>name</id>

<value><wdktags:nodeRef path="."/></value>

</wdk:input>

<wdk:input name="Description">

<wdktags:attachTo path="/domain/description" root="model"/>

<id>description</id>

<value><wdktags:nodeRef path="."/></value>

</wdk:input>
Note that each widget is attached to the attribute it is supposed to display and that the widgets do not contain <required>

| WDK Widgets | 264

tags. Since the component dictionary specifies that these fields are required, it is not necessary to explicitly include
"<required>true</required>" nodes. In this example the <label> tag is not defined either, so any default
label defined for that field in the component dictionary would be used.)
One can override the component dictionary information by providing the appropriate field directly. For example, the
code below would make description required even if the component dictionary says otherwise:

<wdk:input name="Description">

<wdktags:attachTo path="/domain/description" root="model"/>

<id>description</id>

<value><wdktags:nodeRef path="."/></value>

<required>true</required>

</wdk:input>

Chapter

5
Model Objects
Topics:

Overview
Model Object Lifecycle
Defining and Using Model Objects
Initializing and Saving/Restoring
State
Managing Widgets
Special WDK Tags
getModelObject()
<wdktags:execute>
Advanced Model Object
Techniques

This chapter describes how to write and use model objects with WDK application
pages.

| Model Objects | 266

Overview
Saba constructs a WDK page by combining three separate XML pages:

The model file contains the page's data. It describes all the widgets on the page and can also include Java code to
retrieve or construct information.
The view file describes how the information on the page is displayed.
The control file specifies what model and view files are being used. Thus, you can specify a particular application
page by specifying the control file.

You can also associate a model object with an application page. The model object is declared in the control file, and is
used whenever that application page is generated. The model object performs several functions:

The model object can be used to preserve information when a page is reloaded-for example, when a user clicks on
a column header to sort a page's data.
The Saba platform queries the model object to find out whether each widget is visible, and if it is visible, whether
it is editable.
The model object can also function as a command object (if it implements the ICommand interface).
All page parameters are automatically passed to the model object when it is initialized. This can simplify the model
file's code, because you need only to declare a parameter in the model file (with
<wdktags:param><wdktags:param>) if it is used in the model file. If the parameter is only used in the model
object's code, you do not need to declare it in the model file.
Similarly, the model object can pass parameters to the next page, making it unnecessary to declare them as outbound
<wdktags:param> parameters in the model file.
The model object can be used to share information between various pages. For example, if a page redirects to another
page, it can arrange for its model object to persist so the replacement page can call its methods.
You can add any methods you like to the model object, and the model page can invoke the methods whenever it
likes. This can make the model page much cleaner. For example, to make an elaborate calculation, put all the code
into a single model object method and then invoke the method from the model page, rather than putting all the code
in the model page directly.

Note that an application page does not need a model object. It is entirely up to you whether to create a model object for
any particular application page. However, having a model object simplifies many tasks, and there are some things you
can only do with a model object (for example, dynamically deciding whether a particular widget should be displayed).

Model Object Lifecycle


This section describes the life-cycle of a typical model object. Please note that in some circumstances, the life-cycle
may be different. For example, a model object is usually destroyed when the application page closes. There are ways
to make the model object persist longer, so the data can be examined. These techniques are discussed later on as an
advanced topic. However, most cases follow the pattern described here.
1. The user launches a Saba application page. He might do this by, for example, clicking a link in another application
page. This generates an HTTP request to the Saba server. The request specifies which control file should be loaded.
2. The Saba server creates an HttpServletRequest object. Usually, the model object exists only as long as this
request object does.
3. Saba opens the control file, and examines it to find the appropriate model and view files. It can use these to construct
the application page.
4. Saba examines the control file to see if it specifies a model object class. If it does, Saba creates an object from that
class. It calls the object's constructor, passing in the HttpServletRequest object.

| Model Objects | 267

5. Saba invokes the object's decodeState method. This lets the object initialize certain of its values. The object is
passed a stateDecoder object, which contains any public parameters passed to the application page. It may also
contain other information, as noted in Step 9 below.
6. Saba invokes the object's performAction method. This method can take any actions you like. An application
page can usually put most of its Java calculations in this method.
7. If the model page contains Java code which calls on the model object, those calls are processed. In particular, the
model object can implement the ICommand interface. In this case, the model page can invoke the model object to
make it generate an XML node.
8. Saba then queries the model object about every widget specified in the model page. Saba asks the model object
whether each widget is visible. If the widget is visible, Saba then asks whether it is view only, and queries whether
the widget should be initialized from the request object.
Note: Some widgets such as tables can contain other widgets. If a container widget is not visible, none of its
contents are visible, and the model object is not queried about those other widgets.
9. When the user leaves the page, Saba calls the model object's encodeState object. This lets the model object
record its current state information to be passed along to the next object. The object can pass both "public" and
"protected" state parameters. Public parameters are passed to the next page like any other HTTP parameter. Protected
parameters are only passed to the next page if it has the same control file as the current page (that is, if the application
page is reloaded), and in certain other situations described in "Advanced Model Object Techniques".
When the HTTP request object is destroyed, so is the model object. This usually happens when the user leaves the page,
although in certain circumstances, the request object may persist until the next page (for example, if the first page uses
a <wdktags:redirect> tag). As discussed in "Advanced Model Object Techniques", in those circumstances you
can have the second page make Java calls to the first page's model object. In most cases, however, the model object is
called only while its own page is active.

Defining and Using Model Objects


A model object must implement the IModelObject interface. The interface specifies the following methods:

decodeState: Used to initialize the object with any state information, as described in "Initializing and
Saving/Restoring State"
encodeState: Enables the model object to save its state, as described in "Initializing and Saving/Restoring State".
performAction: Called after the object is created and initialized. The object can take any actions here that it
needs to, to prepare itself to handle the other methods.
isVisible, isViewOnly, initializeFromRequest: Called for each widget, to determine whether and
how the widget should be displayed. These are discussed further in "Managing Widgets".

The model object must also provide a constructor that takes a single argument, an HttpServletRequest object.
You can write any other methods and implement other interfaces as needed. For example, many model objects implement
the ICommand interface. This enables them to function as command objects and generate XML for display on the Saba
application page. You can also write methods to perform any calculations you need on the model page.
For details on the various classes and interfaces, see the Java API Reference Guide.

| Model Objects | 268

Declaring a Model Object


To declare a model object, put the following declaration in the control file, as a child of the <rdf:Description>
tag:

<wdk:modelObject>fully-qualified class name</wdk:modelObject>


The class must be in Saba's class path.
For example, a control file might look like this:

<?xml version="1.0" encoding="UTF-8"?>

<?cocoon-process type="wdk"?>

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"

xmlns:wdk="http://www.saba.com/XML/WDK">

<rdf:Description id="1234567">

<rdf:type resource="http://www.saba.com/XML/WDK/Control"/>

<wdk:version>1.0</wdk:version>

<wdk:model rdf:resource="myCustomModel.xml"/>

<wdk:view rdf:resource="myCustomView.xsl"/>
<wdk:modelObject>com.mycorp.model.MyCustomModel

| Model Objects | 269

</wdk:modelObject >

<wdk:widgets

rdf:resource="/wdk/xsl/widget/wdk_widgets.xsl"/>

<wdk:uiframework/>

</rdf:Description>

</rdf:RDF>
Saba automatically creates the model object when the page is created, as described in "Model Object Lifecycle".
To get the model object from Java code within the model page, call the method getModelObject (which returns an
IModelObject). For example, if your model object belonged to the class MyCustomModel and had a method named
doCalculations, you could invoke it from the model file by including code like this:

<xsp:logic>

MyCustomModel model = (MyCustomModel) getModelObject();

model.doCalculations();

</xsp:logic>
In addition, there are several special-purpose WDK tags to cause the model object to perform specific actions. These
tags are described below.

Initializing and Saving/Restoring State


When an application page with a model object is created, Saba calls the object's constructor, passing in an
HttpServletRequest object. This lets the object perform any needed initialization.
After the model object has been created, Saba invokes its decodeState method. This method is passed a
StateEncoder object. This object contains any parameters passed to the object. In addition, if the application page
which was loaded is the same as the page which launched it (that is, if a page reloaded itself), the StateEncoder can
contain special "protected" parameters which were passed to it by the previous object's encodeState method.
When the user leaves the page, Saba first calls the object's encodeState method, then destroys the object. The object
can encode both "public" and "protected" parameters. Public parameters are passed to the next page as HTTP parameters,
and if the next page has a model object, they are passed to that object's decodeState method. Protected parameters
are passed to the next page only if it uses the same control file as the current page (that is, if the page reloads itself).
Saba guarantees to call encodeState before destroying the object. Thus, your handler for encodeState can take
care of any necessary finalization tasks.

| Model Objects | 270

Decoding State
After a model object is created, Saba calls its decodeState method. This method is passed a StateDecoder object,
which contains information about the parameters passed to the page. There are two kinds of parameters encoded in this
object:

public parameters include all HTTP parameters passed to the application page, whether they are specified by the
previous model object, by a <wdktags:param> tag, or some other way. They can be examined by calling the
StateDecoder.getPublicParameter methods.
protected parameters include any protected parameters passed by the previous page's model object. These parameters
are only passed if the previous application page used the same control file, or in the circumstances described in
"Advanced Model Object Techniques". They can be examined by calling the
StateDecoder.getProtectedParameter methods.

The model object's constructor is passed the HttpServletRequest object. Because this object contains all the
parameters passed to the application page, you can retrieve all those parameters in the constructor instead of waiting
until decodeState is called.

Encoding State
When a user leaves an application page, Saba calls the model object's encodeState method immediately before
destroying the object. The method is passed a StateEncoder object. The model object can store any parameters into
this object. The parameters stored can be either public or protected:

public parameters are passed to the next page as HTTP parameters. Thus, they can be used to pass information to
the succeeding application page, or even to a non-Saba web page. In addition, if the succeeding page has a model
object, the parameters are passed to that object's decodeState method.
protected parameters are passed to the succeeding page only if it uses the same control file as the current page (that
is, if the page is reloading itself). Otherwise, they are discarded.
Note: There are other circumstances where protected parameters are made available to a succeeding page, even
if it does not use the same control file. These circumstances are described in "Advanced Model Object
Techniques".

Managing Widgets
You can use a model object to manage the widgets on a page. If an application page has a model object, Saba queries
that object to find out if each widget should be displayed, and if so, whether the widget should be editable, and whether
it should be initialized from the request object. This happens after Saba creates the model object and invokes its
decodeState and performAction methods.
Saba identifies each widget with a WidgetInfo object. This object contains the widget's name (specified by the
widget's <name> tag) and its form ID (which is usually the value of the <id> tag, or of the <name> tag if there is no
<id>).
For each widget, Saba does the following:
1. Saba calls the model object's isVisible() method, passing in a WidgetInfo object which identifies the widget.
If the method returns false, the widget is not displayed, and none of the other methods are called. If the method
returns true, the widget is displayed normally.
Note: If the widget contains other widgets, and it is not visible, none of the contained widgets are visible. For
example, if the model object says that a particular table widget is not visible, the table is not displayed, which

| Model Objects | 271

means none of the widgets in the table are displayed. Neither initializeFromRequest() nor
isViewOnly() is called for any of those widgets.
2. If the widget is visible, Saba checks to see if the widget's definition in the model page contains an
<initializeFromRequest> tag. If the tag is present, Saba obeys it. If the tag is not present, Saba calls the
model object's initializeFromRequest() to find out whether the widget should be initialized from the request
object. (For more information about initializing widgets from the request object, see
"<initializeFromRequest>""<initializeFromRequest>" on page 4-8.)
3. If the widget is visible, Saba checks to see if the widget's definition in the model page contains a <viewOnly> tag.
If the tag is present, Saba obeys it. If the tag is not present, Saba calls the model object's isViewOnly() to find
out whether the widget should be displayed in a view-only state.
Note that the initializeFromRequest() and isViewOnly() methods are called only if the model page does
not specify, through the appropriate XML tags, whether the widget should be initialized from request and/or is view
only.

Special WDK Tags


The Saba WDK provides several Java methods and model file tags to manage interaction with the model object.

| Model Objects | 272

getModelObject()
You can use this method from any Java code in a model page (for example, within an <xsp:logic> tag). It returns
the model object for the page. For example, if your model object has a method named doCalculations, you could
invoke it with this code:

<xsp:logic>

IModelObject thisPageMO = getModelObject();

thisPageMO.doCalculations();

</xsp:logic>

<wdktags:execute>
The <wdktags:execute> tag provides a useModelObject attribute. If this attribute is present and set to "true",
the model object is invoked as a command object, generating XML. (The model object must implement the ICommand
interface.) Using the attribute is the equivalent of using code like

<xsp:logic>

IModelObject thisPageMO = getModelObject();

</xsp:logic>

<wdktags:execute commandObj="thisPageMO">
For more information about executing commands through command objects, see the section on
"<wdktags:execute>""<wdktags:execute>" on page 3-24.

Advanced Model Object Techniques


This chapter describes the standard way of using a model object. These are enough for most situations. However, there
may be circumstances when you want more flexibility. This section describes certain special features provided by the
model objects.

| Model Objects | 273

Calling the Model Object From a Second Page


As we discussed in "Model Object Lifecycle", a model object is usually associated with a particular page. When the user
leaves the page, the model object stores its state and is destroyed. Usually, the only code that calls a particular model
object is the code on that page. Thus, there is no reason to preserve the model object after the page finishes.
However, there may be times when you want one page's model object to be accessible to another page. For example, a
page that uses the <wdk:redirect> tag might want to make its model object to the page it launches, and a page that
opens subpages (by using the page widget) might want to make the outer page's model object available to the subpages.
To do this, assign a key to the model object. When you declare the model object in the control file, add a key attribute:

<wdk:modelObject key="key_name">model object class</wdk:modelObject>


Note: A model object is always destroyed when the request which creates the page is finished. Thus, you can only
call the model object from a second page while the first HTTP request is active. This can happen if the first page
automatically launces a second page (with a <wdk:redirect> tag), or if a page contains embedded pages.
To access the object from a later page, use a different version of the getModelObject method, which takes a single
string as a parameter:

<xsp:logic>

IModelObject otherModel = getModelObject("key_name")

</xsp:logic>
As soon as you have the earlier page's model object, you can access any of its public methods and fields, as with any
other object. Depending on what class is making the calls, you can also access the model object's package-level, protected,
or private methods and fields. If you are calling from Java code embedded in the model file, that code is compiled as a
class that is in its own package and object hierarchy and cannot call any protected or private methods.

Passing Protected State to Another Model Object


When a model object stores its state, it can store two kinds of parameters: public and protected. As described above (in
"Initializing and Saving/Restoring State"), the difference between the two usually is:

Public parameters are available to the successor page, no matter what it is. They are passed as HTTP parameters,
and if the successor page has a model object, they are passed to its decodeState method.
Protected parameters are only made available to the successor page if the two pages use the same control file. That
is, if the page reloads itself. Otherwise they are inaccessible to the successor.

There are times when you might want a page to be able to pass protected parameters to a small number of other pages.
For example, you might design a "wizard", where the user goes through a sequence of pages to perform an action. In
such a case, you might want each page to pass its full state information to any other page in the wizard, so the user could
jump from step 3 back to step 1 and see all the information he entered on that page.

| Model Objects | 274

You can do this by making the model object part of a model group. You do this by adding a group attribute to the
model object's declaration in the control file:

<wdk:modelObject group="group_name">model object class

</wdk:modelObject>
If a model object belongs to a group, its decodeState method can read protected parameters which were passed by
any model object in that group.
For example, suppose a multi-page wizard has a model object for each page, named WizModel1, WizModel2, etc,
with all the model objects belonging to the "WizModelGroup" group. If a user is using the first page of the model,
and the user leaves the page, the model object stores its state in the StateEncoder, as public and protected parameters.
When the successor page launches, its model object's decodeState method is called. If the successor page is not a
page in the wizard (because the user went to another part of the application), Saba sees that the successor page's model
object does not belong to the same model group. That page receives the public parameters, not the protected ones.
However, if the successor page is another page in the wizard (because the user clicked the Next button), Saba sees that
the new page's model object belongs to "WizModelGroup", like the preceding page. In that case, the successor page's
decodeState is able to examine both the public and the protected parameters.
Note: You should note the difference between assigning a model object a key (as discussed in "Calling the Model
Object From a Second Page") and making it part of a group. If a model object is given a key, that lets successive
pages interact with the model object itself, calling its methods and examining its fields. If a model object is part of
a group, the successive pages do not interact with the model object directly-the model object is destroyed normally
when they user leaves the page. Instead, the successor pages (if their model objects belong to the same group) can
examine the protected state information which the first object stored before it was destroyed.

Chapter

6
Coding Customization (Java)
Topics:

Types of Objects
Typical Tasks
Executing Java Code

This section describes how to write Java code for use with WDK pages. This
code can retrieve information from the Saba data store and also add new
information.

| Coding Customization (Java) | 276

Types of Objects
The Saba API defines classes designed to manage a wide range of different kinds of data. For example, objects based
on the RoleManager and RoleDetail classes retrieve and modify information about Saba roles. In addition, the Saba API
defines several infrastructure classes that you use with all kinds of data.

Infrastructure Classes
These objects provide core functionality for using the API.

ServiceLocator
This is a general utility class. It provides several methods for creating and managing delegate objects. Many of the
methods are intended for Saba internal use only. However, three methods are useful to API programmers:

getManager(): Creates the specified Manager object. This method is used to create stateless Managers (that is,
Manager objects without a corresponding ManagerHome).
getHome(): Creates the specified Home object. This might be the Home for a package which uses the Home/Entity
model (as described in "Delegate Objects"), or the ManagerHome for a Manager object which contains state
information.
getReference(): These methods construct a Reference object, based on the values you supply. (For example,
one method constructs the reference object from passed class and primary key objects, another from class and object
ID, etc.) These methods do not verify that the values passed correspond to data in the Saba data store. They construct
appropriate reference objects based on the assumption that the parameters are valid. (This makes the methods very
useful when you know the reference object is valid, because the methods save the cost of a network database query.)

When you create a ServiceLocator object, provide an authentication certificate (which you can get from
SabaLogin.authenticate, or by having it passed to your WDK page). This lets the ServiceLocator set the
appropriate permissions for delegate objects that it creates. Note, however, that the getReference method is static
and can be run without instantiating ServiceLocator-thus, you can call getReference() without having an
authentication key.

Delegates
This special-purpose class defines constants for each delegate provided by the Saba API. Each of these constants is
implemented as a static member of the Delegates class and is also identified by a static field in that class. These
constants can be passed to the various ServiceLocator create methods to identify what kind of delegate you want
to create. For example, to create a PartyManager object, call the ServiceLocator.getManager() method:

// Assume we have a ServiceLocator object, "locator"

PartyManager partyManager = (PartyManager)

locator.getManager(Delegates.kPartyManager);
You can find the constant for a particular delegate by consulting the Delegates reference page.

| Coding Customization (Java) | 277

SabaLogin
This object enables you to generate a ServiceLocator, which identifies which user has logged in and authenticates
that user's session.. Many Saba objects require a ServiceLocator. The object lets Saba know which user account
is performing the action, and thus verify that it has permission to do so. (For example, a Manager object might not let
a user delete data from the data store if the user does not have the right permissions.)
You do not usually need to use a SabaLogin object directly. You can always call the getServiceLocator() method
within a WDK page or a Command object to obtain the current valid ServiceLocator object. However, in some
circumstances, you might find it useful to be able to log in directly. For example, if you are testing Java code, you might
want to generate a ServiceLocator within the code, thus letting you run the code in a standalone application (instead
of launching the code from a WDK page). To generate a ServiceLocator, call one of the static
SabaLogin.authenticate methods.

IPrimaryKey
Each Reference Object (described on page ) can be identified by a Primary Key object. This object, which implements
the IPrimaryKey interface, should be treated as an opaque token. That is, you can find out the primary key of a
particular reference object by calling its getPrimaryKey() method. You can find the reference object for a particular
primary key by calling ServiceLocator.getReference(), or the appropriate delegate object's
findByPrimaryKey().
In previous Saba4 releases, reference objects were identified by Object ID strings. Some API packages still use these
strings. You can convert an Object ID string into a Primary Key by constructing an ObjectId object (which implements
IPrimaryKey). Pass the ID string to the ObjectId(String) constructor. Similarly, if you ever need to convert
a primary key into an Object ID string, you can do this by calling the primary key's toString() method. However,
whenever possible, you should use primary key objects instead of object ID strings.

Class Structure
Each API package has several related objects. The objects can be divided into two broad categories:

Delegate objects: These objects interact with the Saba data store. One example of a "delegate object" is a Manager,
which you would use to create, find, or modify information in the data store.
Helper objects: These objects contain information from the Saba data store but do not directly interact with it. They
are created by delegate objects, and passed to delegate object methods, but altering a helper object's instance data
fields does not directly change the underlying data in the Saba data store.

Helper Objects
In most packages, the helper objects come in pairs:

Reference objects, which identify some small collection of persistent data in the Saba data store, such as all the
information about a client.
Detail objects, which contain a copy of the relevant data fields. For example, a ClientDetail contains information
about a client, such as its name, time zone, address, etc. Very frequently, detail objects contain other detail objects
(for example, a ClientDetail has, as one of its fields, an AddressDetail) and/or reference objects.

Reference Objects
A reference object corresponds to some small collection of persistent data in the Saba site's data store. For example, a
particular Saba site might have a thousand employees listed. Information about each employee is kept in the data store.
The reference object uniquely identifies the set of data, but it does not itself contain the data. Instead, it provides the

| Coding Customization (Java) | 278

information that you need to retrieve the data from the Saba data store. For example, to read or change a particular
employee's information, use a manager object to retrieve the appropriate employee reference object. When you have the
reference object, you can use other delegate object methods to retrieve or change the underlying data. The most common
way to find a reference object is by calling a method in the appropriate delegate object. However, particular objects
might provide other ways. (For example, a "course" object might provide a method to return the appropriate "instructor"
object for the course.)
Each reference object is uniquely associated with a primary key. You can get the primary key by calling the object's
getPrimaryKey() method (inherited from the IReference interface).
You should never create reference objects by calling the constructor. If you need a particular reference object, you get
it by calling some other object's method (usually a Manager method), or by constructing it with the
ServiceManager.getReference() methods.
Note: There are two different ways of getting a reference object when you know its primary key: You can call the
delegate object's findByPrimaryKey() method, or you can call ServiceManager.getReference().
There are two differences between these approaches. First, the delegate objects' findByPrimaryKey() methods
check to make sure that the reference object exists (that is, that the primary key corresponds to data in the Saba
data store). ServiceManager.getReference() constructs the reference object by using the primary key
and the specified class, without checking the data store. Thus, if you know that the primary key is valid, you should
use ServiceManager.getReference(), because the method does not incur the cost of a database check.
Second, because ServiceManager.getReference() is a static method, you can use it even if you do not
have a session key. In order to create a delegate object, you need an instantiated ServiceManager object, which
means you must have a valid session key.

Detail Objects
Some delegate objects provide methods to retrieve or change specific data fields from the Saba data store. However,
these operations are expensive, because each one requires a separate database query over the network. For this reason,
most API packages provide detail objects. A detail object contains all the data fields for the reference object. In most
cases, you get detail objects by calling a method in the appropriate delegate object. The manager retrieves all the data
fields with a single database query, constructs the detail object, and returns it.
Detail objects also implement IReference. Every detail object is also a reference object. If you need a reference
object, you can cast the appropriate detail object to IReference.
Most detail objects implement the IXMLObject interface. This means you can generate an XML representation of the
detail object by visiting it with an appropriate IXMLVisitor object.
There are times when you create a detail object directly, by calling its constructor. For example, as noted above, the
usual way to add an object into the Saba data store is to create a new detail object, fill in its fields appropriately, and
pass it to the manager's "create" method.

Delegate Objects
Delegate objects are used to interact with the Saba data store. You can use delegate objects for several purposes. For
example, you can search for a reference object meeting certain requirements ("Find all clients whose names contain
'Smith'"), find the detail object for a particular reference object, or add a new object into the data store. In all cases, you
retrieve the appropriate delegate object by calling a method in the ServiceLocator object. You do not create the
delegate directly, by calling its constructor. (In most cases, only the delegate's interface is part of the API. When you
call the ServiceLocator method, it returns an object which implements the interface.)
Different Saba packages use two basic models of delegate objects: Manager delegates, or Home/Entity delegates.

If the package follows the Home/Entity model, it contains a pair of delegate classes: a Home class (for example,
ClientHome), and an Entity class (for example, ClientEntity). The Home object provides methods for creating
a reference object (and the corresponding data in the Saba data store), finding a reference object by its primary key,

| Coding Customization (Java) | 279

and deleting a reference object (and its corresponding data). The Entity object provides methods to retrieve or change
information about a given reference object.
If the package follows the Manager model, it provides a single Manager class (for example, CurrencyManager).
This manager provides all the delegate functionality: it provides methods to find a specific reference object, retrieve
or modify the object's data, delete the object, etc. It may also provide special-purpose methods needed by that specific
package. In some cases, the package also contains a ManagerHome class, used to create the Manager object (if the
Manager contains state information).

An API package uses the Home/Detail model if the delegate objects provide simple, straightforward functionality
(searching for an object containing specific information, modifiying data, etc.). If the package needs to provide more
elaborate delegate functionality, it uses the Manager model. In addition, some packages use the Manager model so a
single Manager can handle a wide number of helper objects. For example, the PartyManager object is used to create
and modify Client objects, Employee objects, etc.
Manager Objects
A manager object creates and retrieves a particular kind of object. For example, to find, modify, or create a Saba user,
use a PartyManager object.
The API specifies a manager interface, not the class of the manager. You retrieve a manager object by requesting it from
a ServiceLocator. For example, to create a new employee, you need a PartyManager. You get this by calling the
ServiceLocator's getManager method, passing in the appropriate constant (specified by the Delegates class).
The method returns a manager object which implements the PartyManager interface. You can then use that object
to perform the various operations.
The manager object provides a wide range of functionality. Different manager objects provide different methods, but
the typical functions include:

Find the reference object with a particular primary key


Find all reference objects meeting certain criteria
Find the detail object for a particular reference object (either by passing the reference object itself, or its primary
key)
Find all detail objects for reference objects meeting certain criteria
Create or delete reference objects (thus changing the information in the persistent data store)

You do not create manager objects directly. There are two different ways to create a Manager object, depending on the
API package:

If the Manager does not contain state information (the most common case), call the ServiceLocator's
getManager() method.
If the Manager contains state information, you must use an appropriate ManagerHome object to create the Manager.
(For example, you would use LearningOrderManagerHome to create a LearningOrderManager). In this
case, you would use the ServiceLocator's getHome() method to retrieve the ManagerHome, and use the
ManagerHome's appropriate method to retrieve the Manager.

To see which method to use, consult the API reference documentation and see if there is a ManagerHome class
corresponding to the Manager you want to create. If there is, use it to create the Manager. If not, use the
ServiceLocator.
Home Objects
If the package follows the Home/Entity model, use an appropriate Home object to manage Reference objects (and their
underlying data in the Saba data store). Each Home object provides three functions:

create(): Creates a reference object, storing its data in the Saba data store. Usually, the method is passed an
appropriate Detail object containing the data for the new object. However, some Home classes' create() methods
may be passed the information in different ways (for example, in a Vector, or as separate parameters).
findByPrimaryKey(): Finds the reference object with a specific primary key. (Each reference object is uniquely
identified by a primary key.)
delete(): Deletes the specified reference object, along with its underlying data in the Saba data store.

| Coding Customization (Java) | 280

In addition, many Home objects provide other "find" methods, which find all reference objects which meet certain
criteria (for example, "find all active deliveries"). These methods usually return all matching objects in a Collection.
For details, see the API reference page for the appropriate Home object.
To get a Home object, call the ServiceLocator's getHome() method.
Entity Objects
If the package follows the Home/Entity model, use an Entity object to find or change a reference object's underlying
data (after first using the Home object to find the appropriate reference object). Entity objects have various different
methods to set and change the data. For example, many Entity objects have getDetail() and setDetail()
methods, which let you get or change several fields at once. In addition, some Entity objects provide methods to retrieve
or change a single field in the underlying data store (such as ClientEntity.getDisplayName(), which enables
you to retrieve the client's display name and not its other fields).
To get an entity object, call the reference object's getEntity() method.

Typical Tasks
There are several tasks which you might want to perform with the API objects. This section describes some of the
operations you might perform.
In each example, assume that you have already instantiated a ServiceLocator object, which you can then use to
create any necessary delegates. You might create a ServiceLocator this way:

//...get a session key

ServiceLocator myLocator =

SabaLogin.authenticate(username, password);

Finding a Specific Reference Object


One of the simplest tasks is finding a particular collection of data in the Saba data store. Once you have this information,
you might process it and render it as XML (for an application page to display), edit it, export the information in some
way, etc.
As noted above, there are two different ways to find a reference object if you know its primary key:

You can call one of the ServiceLocator's static getReference() methods. This method constructs a reference
object from the primary key and the class object. It does not check to see if the primary key is valid, and thus, if the
data exists in the Saba data store. That makes this method preferable if you know that the key is valid, because you
save the cost of a networked database query.

| Coding Customization (Java) | 281

For example, to find the Employee object with a particular primary key, you would call

Employee employeeRef =

(Employee) ServiceLocator.getReference(Employee.class,

empPrimaryKey,

"Severus Snape"); //display name


Similarly, you could call the appropriate getReference method to construct a reference object based on its object
ID. (If you use getReference(), you do not need an authentication certificate.)

To verify that the object exists (that is, that the primary key is valid), call the appropriate delegate object's
findByPrimaryKey method. For a particular class of reference object (for example, "Foo"), the appropriate
delegate object is usually either a Manager ("FooManager") or a Home ("FooHome") object. In either case, it is
created by the ServiceLocator.
For example, to create an employee reference object, you might write code like this:

EmployeeHome myEmpHome =

(EmployeeHome) myLocator.getHome(Delegates.kEmployee);

Employee employeeRef =

(Employee) myEmpHome.findByPrimaryKey(empPrimaryKey);
In this case, the delegate object verifies with the Saba data store that the primary key is valid, and the information
exists in the database. This makes the method much slower, but more appropriate if you are not sure that you have
a valid key.
Once you have the reference object, you can use it to find the detail information, or perform other operations on the
data.

Finding all objects meeting certain criteria


The above section describes how to find a reference object if you know its primary key. If you do not know the primary
key, you would usually find the reference object by using one of its delegates. The various delegate objects provide a
wide range of methods for finding objects based on various criteria.
The precise format of the method varies from package to package. In general, the methods return a Collection of
objects meeting the specified criteria. The objects returned (depending on the method) might be Reference objects,
Detail objects, or some method-specific format (such as a Vector of strings.)
For example, suppose you wanted to find an employee with the last name "Hagrid". In this case, the best method to use
is PartyManager.findEmployee(). This method is passed several String parameters, which restrict the search.

| Coding Customization (Java) | 282

It returns a Collection of Vectors, each of which contains various data fields for the employee (such as first name,
last name, object ID, etc.).

PartyManager myPartyMgr =

(PartyManager) myLocator.getManager(Delegates.kPartyManager);

Collection hagridEmps = myPartyMgr.findEmployee(

"", // first name--don't restrict

"Hagrid", // last name--only return matching employees

"", // user-name--don't restrict

"", // organization--don't restrict

"", // location--don't restrict

""); // employee number--don't restrict


The method returns a Collection of Vector objects-one for each employee with the last name "Hagrid". (You
could further restrict the search by filling in some of the empty strings above.) The Collection might be empty, if
there are no matching employees.
In the case of this method, each Vector is a collection of Strings in a specified order (documented in the delegate's
reference page). You can search through the return values, looking for the particular employee you want. Once you've
found the specific employee information, you can translate it into a reference object. (For example, in the case of
findEmployee(), the last string in the vector is the object's ID. You can use ServiceLocator.getReference()
to create a reference object from the ID. You can then use the reference object to modify the employee information, as
described below.)

Modifying an Object
In most cases, the package's Manager or Entity delegate provides methods for modifying an existing object. Follow
these steps:
1. Find the reference object corresponding to the data you wish to modify.
2. Retrieve the detail object for that reference object (by using the appropriate method in the Entity or Manager object).
3. Modify the detail object appropriately. (Be sure to change only the fields you want to change in the underlying data
store!)
4. Call the appropriate "set detail" method (again, provided by the Entity or Manager). Pass the reference object and
the modified detail object. The Saba engine updates the information in the data store.

| Coding Customization (Java) | 283

Creating a new object


This task can be the trickiest. It is much like modifying an existing object. Create an (empty) Detail object by filling it
with appropriate values, and pass it to the delegate's "Create" method. However, in practice, this can be difficult for
some packages. You may not be able to determine what appropriate values are for some of the fields in the Detail object.
If you need to create new objects, you should first consult the reference documentation for the appropriate delegate
object. That should tell you everything you need to know. If it does not, look through sample code to see if there are
any examples of that type of object being created. You may be able to fill in any missing facts there.

Executing Java Code


There are two main ways you can generate API code: You can integrate it with a WDK application page, or you can
run it as a stand-alone Java program.
Most of the time, execute your finished code as part of a WDK page. Standalone code is more useful for testing parts
of your code during the development process. However, you may sometimes want to write an entire Java program which
interacts with the data store through the API. For example, you might execute a one-time batch process by writing special
Java code.

Integrating with WDK pages


By far, the most common use of the API is to write custom application pages. Note that most of the time, you should
be able to customize application pages without writing any Java code. For example, if you would like to change how a
particular application page displays data, you can probably do this by creating a new view page (as described in Chapter
3, "Scripting Customization (WDK)"Chapter 3, "Scripting Customization (WDK)"). However, for more elaborate tasks,
you may need to write Java code and integrate it with the application pages.
There are four basic ways of launching custom Java code from a model page:

By using an <xsp:logic> tag This is the preferred way to execute Java code which produces side-effects, but
does not produce any output in the model page.
By using an <xsp:expr> tag This is the preferred way to execute Java code which generates output other than an
XML node.
By using a <wdktags:execute> tag This is the preferred way to generate an XML node in the model page.
From a model object If an application page has a model object, Saba automatically instantiates the object when it
builds the page. Saba then calls several of the object's methods. For example, it calls a method to have the object
restore any state and/or handle passed parameters. In addition, you can write any additional methods in the object
that you like, then call the model object's methods from inside the application page (for example, from inside an
<xsp:logic> tag). For more information about using model objects, see Chapter 5, "Model Objects"Chapter 5,
"Model Objects".

Note that while <xsp:logic> is the preferred way to execute code which produces side effects without output, it is
perfectly acceptible to use one of the other tags if you want to produce side-effects as well as output. For example, if
you want to delete some information from the data store, you might do it with <xsp:logic>. If, on the other hand,
you want to modify data in the data store, you might choose to do it with <wdktags:execute>, and generate an
XML node containing the modified data.

| Coding Customization (Java) | 284

<xsp:expr>
You can encode a Java expression in a WDK page by using an <xsp:expr> tag. When the model page is processed,
the Saba engine evaluates the Java expression, and replace the tag with the expression's value. This is the preferred way
to insert text into a model page.
For example, if a model page contains the node

<sum><xsp:expr>2 + 3</xsp:expr></sum>
the Saba engine processes it, resulting in this node:

<sum>5</sum>
That node would be available to view pages which drew on the model page.
If you want to insert an XML node, the preferred way is with a <wdktags:execute> tag.

<xsp:logic>
This is the preferred way to launch Java code which does not result in output on the model page. Any code within this
tag is executed. If you want to output results to the page, use either <xsp:expr> or <wdktags:execute>.

<wdktags:execute>
The <wdktags:execute> tag (described on page 3-24) enables you to run Java code which produces an XML node.
This tag identifies a particular command object (either directly, or by invoking a command manager object). Each time
the model page is loaded-that is, each time a control page which references the model page is viewed-the WDK engine
instantiates the command object and invoke its appropriate execute() method, passing the application page's HTTP
request and a visitor object, as well as any specified arguments. The command object can perform any appropriate tasks,
creating API objects and interacting with the data store as necessary. When the execute() method returns, the WDK
engine examines the passed visitor object to generate an XML node. That node is inserted into the generated model
page, replacing the <wdktags:execute> node.
Note: The command object is always passed the HTTP request object for the application page which contained
the <wdktags:execute> tag. This request object contains, as one of its parameters, the user's session key.
Thus, your command object does not usually need to use the SabaLogin object to generate a session key.
The most common function for a command object is to build and generate XML data (using the visitor object), which
is then inserted into the model page. For example, a command object might search for a particular detail object, use the
visitor to build up an XML representation of the object, then return. (If the detail object implements IXMLObject,
generating the XML node can be as simple as calling the Vistor object's visit method and passing it the detail object.
Otherwise, you might have to examine the fields one by one, and call appropriate visit methods for each one.)
Similarly, you might use a command object to generate an entire table of data. The command object might search for
all objects meeting certain criteria (say, all employees in a particular department). It could then use the visitor object to
visit each object in turn, generating a single, large XML node with a subnode for each detail object. The node would be
placed in the model page, and view pages could display the information in a table (perhaps by using a table widget).
While a command object must produce an XML node, that need not be its sole, or even its primary, function. You might
write a command object whose main job is to perform some operation on the data store, but which also generates an
XML node as output. (For example, if a node modifies some data, you might generate an XML node containing the
modified data.) You might do this even if you don't intend to display the data in the node. This leaves the option open
if another developer wants to modify the view page to show the data. It also can be very useful for debugging, because

| Coding Customization (Java) | 285

one debugging option allows the developer to view the intermediary XML pages (and thus examine the precise nodes
returned by the command object).
Note: You can also turn a command object into a Saba Web Service. This enables you to publish the object's XML
output through a SOAP connection to remote clients. For more information about creating web services, see
***XREF web serv. appendix***

Notification Actions
The system administrator can configure Java to perform a notification action whenever a specified event occurs. (For
example, the administrator might set up an action which sends an email to a particular person whenever a new learning
event is created.) Notification actions are discussed in the Notifications Administrator Guide.
Saba ships with several predefined Java actions. In addition, you can write your own Java actions and have them installed
in the system. Once you have done this, the system administrator can associate them with system events, as with the
pre-defined notification actions. When the specified event occurs, Saba invokes the corresponding action object's
executeAction method.
To create a new notification action, follow these steps:
1. Write a new class which implements the com.saba.notify.IJavaNotificationAction interface. Place
the notification code in that class's executeAction method. (For more information about this interface, see the
Web API Reference.
2. Install the notification action class into the Saba application's class path. Register that notification action with Saba,
as described in the System Administrator Guide.
3. Using the Saba System Administration module, associate the notification action with any appropriate events.
Whenever the specified events occur, Saba finds an object of the appropriate class and invoke its executeAction
method.
Note: Saba makes no guarantee as to when the notification object are created, how long it exists, or when it is
destroyed. For example, Saba might choose to create a single instance of a notification action class, then call its
executeAction class whenever an appropriate event occurs. Conversely, Saba might choose to create a fresh
instance of the class every time the specified event occurs, call its executeAction method, then destroy the
object. You cannot make any assumptions about how long a particular notification object might persist after it
finishes executing its executeAction method.

Non-WDK Programming
The most common reason for using the Saba API is to modify or create Saba application pages. However, there are
circumstances in which you might want to execute API code on its own.
The most common circumstance is when you are testing new code. Indeed, you'll probably want to test all your code
with standalone Java applications, before you try integrating it with WDK model pages.
In addition, you might sometimes want to create special, stand-alone applications which use the Saba API. One
circumstance when this might be useful is if you need to perform a large, one-time batch operation on the Saba data
store. For example, if your company has a reorganization, you might need to assign a large number of employees to new
departments. The simplest way to do this might be to write a quick Java program which retrievees each affected user's
record, displays his or her information to the administrator, and lets the administrator choose a new department. For
such an operation, it might make more sense to write the program entirely in Java, and not use the Saba application page
interface at all.

| Coding Customization (Java) | 286

The API example code shows how to do this. Create a test Java class with a main() method which generates an
authentication certificate, then tests your objects:

public static void main(String[] args) {

/* In a WDK page, you could let the Saba engine catch the

* exceptions and show an appropriate error message. In test

* code, you'll probably want to catch the exceptions yourself.

*/

try {

/* login and obtain a ServiceLocator*/

ServiceLocator locator =

SabaLogin.authenticate(kUsername, kPassword);

// Now, run your test code.

// ... ... ...

catch (Exception e) {

e.printStackTrace();

System.exit(0);

| Coding Customization (Java) | 287

} /* main */

Chapter

7
Tools and Utilities
Topics:

WDK Page Compiler


Java ANT Makefile

This section describes several tools and utilities that are shipped with the Saba
Cloud product.

| Tools and Utilities | 290

WDK Page Compiler


Before the system can display an application page, it has to compile the page's model file into a Java class. Objects from
that class generate the data for each use of the application page. This compilation has to be done only once for each
application page. When the model file has been compiled, the system can create an instance from the Java class each
time the application page is loaded by any user.
If a user tries to open an application page which has not yet been compiled, the system compiles the model file at that
time. However, this increases the load time for that application page. As noted, this is a one-time cost, and the next time
the user loads the application page the response is much faster. However, the page load time can be frustrating for the
user.
For this reason, the product ships with a page-compiler utility. You can use this utility to compile all the application
pages at once, so the compiled Java classes are ready to go whenever needed.
The compilation utility is called compilepages.bat on Windows machines (and compilepages.sh on Unix
machines).
If you call the compilepages script without any parameters, the script compiles all application pages for your default
site, storing the results in the appropriate directory in your application server's repository. (It finds these default locations
by retrieving the values from your installation's environment.bat file.) Usually, this is all you need to do.
The utility's full syntax is:

compilepages [-siteURL SITE_URL_NAME] [-d DIRECTORY]


[-page PAGENAME] [-srcRoot EAR_OR_SOURCE_DIR]
[-repository REPOSITORY_DIR]
Table 6-1:) compilepages optional parameters
Parameter

Description

-siteURL

The URL of the Saba Cloud site whose pages you want to compile. If not specified, default
site is used.

-d

Directory containing application pages to compile, relative to the -srcRoot parameter. If


not specified, uses the source root directory.

-page

Path to a specific application page's control file, relative to the directory specified with the -d
parameter. If not specified, all application pages in that directory are compiled.

-srcRoot

Location of archive (.ear file) or directory containing application pages to compile. If not
specified, site's saba.ear archive is used.

-repository

Location of application server's directory where the compiled application pages should be
stored. If not specified, default directory is used.

| Tools and Utilities | 291

Examples
Compile all pages in the default site's saba.ear file, and store the results in the default application server directory:

compilepages
For the system's Main site, compile all pages in the specified archive, and story the results in the specified directory:

compilepages -siteURL Main -d / -srcRoot c:\sabaweb\lib\saba.ear


-repository C:\bea\user_projects\mydomain\repository
Compile the specified page from the installation's Main site (loading page from saba.ear, and storing in default
location):

echo

compilepages -siteURL Main -page /wdk/samplesexpando.rdf

Java ANT Makefile


Saba Cloud ships with an Ant script which you can use to compile custom Java classes into a Customization.jar
archive, which you can then place in your application server's class path. The script is named customization.xml.
To use this script, follow these steps:
1. Download and install the Apache Ant utility, version 1.6.1, available from
http://ant.apache.org/bindownload.cgi
2. Modify the customization.xml file to set the correct value for the SABA_INSTALL_PATH and
SABA_EARFILE properties.
3. Create your custom classes in the com.saba.custom package and directory structure.
4. To compile your classes into a standalone Customization.jar, execute the command

ant -buildfile customization.xml


There are several other compile options you can use with this script. To update the saba.ear with your code, use the
following options.

ant -buildfile customization.xml update_ear


Note: We recommend that you stop your application server before using the update_ear option.

| Tools and Utilities | 292

To compile your code without packaging it into a Customization.jar, run:

ant -buildfile customization.xml compile


To update the saba.ear with customized page labels (as created with the Eclipse plug-in), run:

ant -buildfile customization.xml package_labels


To remove all your previous compilations, run:

ant -buildfile customization.xml clean

Chapter

8
Designing Portlets
Topics:

Overview
Writing a Portlet WDK Page
Coding a Portlet Manager
Installing a Portlet

| Designing Portlets | 294

Overview
Saba Learning enables users to set up portal pages, which can contain several special?purpose portlets. A portlet is a
small, self-contained application page that is designed to be displayed along with other portlets in a portal. Usually, a
portlet contains a small amount of information or lets a user perform a simple task (such as searching for something).
It should not contain too much information or be too complicated. For example, a "search for learning offering" portlet
might contain a single text box, where a user could type a text string. The portlet would then launch an application page
showing the search results.
A portlet is very similar to a WDK page. To design a portlet, you have to do the following things:
1. Create a WDK page for the portlet. This must be a control page, view page, and model page, as described in Chapter
3, "Scripting Customization (WDK)"Chapter 3, "Scripting Customization (WDK)". The control and view pages for
a portlet have some small special requirements. The model page is exactly like an application page's model page,
and indeed the same model page could be used by both a portlet and an application page.
Note: You can use any page that returns a well-formed xHTML fragment, instead of a WDK page.
2. Create a PortletPageManager class for the portlet. Usually this is done by extending the
AbstractPortletPageManager, providing an init method. This object specifies which WDK page is used
for the portlet. It can also, optionally, specify parameters and a help page for the portlet.
3. Install the portlet in Saba. This is done using the Portlets Creation Screen, as described in the System Administrator
Guide.

Writing a Portlet WDK Page


A portlet is a special kind of WDK page. Except as noted below, the syntax is exactly as with any other WDK page.
However, you should keep in mind that a portlet is displayed on a portal page with several other portals, and you do not
have control over the page's precise size or dimensions. For that reason, you should try to keep your portlets simple and
straightforward.
Like an application page, a portlet is composed of three different files:

A control file, which specifies the model and view pages for the portlet, as well as any widget used by the view page.
A model file, which specifies the data presented by the web page, and specifies the widgets available for the page.
A view file, which specifies how the page presents its data.

The model file is precisely like an application page model file (and indeed, you could have a single model file be used
by both application pages and portlet pages). The control and view files are slightly different from application page
equivalents. The differences are described below.
For full information about authoring WDK application pages, see Chapter 3, "Scripting Customization (WDK)"Chapter
3, "Scripting Customization (WDK)".
Note: The portlet page does not have to be a WDK page. It can be any page which returns a well-formed xHTML
fragment. For example, it could be an xHTML file, or a CGI script which returns appropriate xHTML code. This
may be a good solution if you are writing a portlet which does not need access to the Saba data store (for example,
a stock-ticker portlet).

| Designing Portlets | 295

Portlet Control Files


A portlet control file is like an application page, with one difference. The cocoon-process node must be of type
"portlet". Thus, the first two lines of the file are:

<?xml version="1.0" encoding="UTF-8"?>

<?cocoon-process type="portlet"?>
(In an application page, the second line would contain type="wdk".)
In fact, if you change the type value to wdk, the portlet page becomes an application page, and can be launched and
tested like any other application page; as noted below in "Testing a Portlet Page", this is the preferred way to test a
portlet page before deploying it.

Portlet View Files


A portlet is usually displayed in a small area on a portal page, along with several other portlets. You cannot now the
precise size or shape of a portlet when you design it. You should keep these restrictions in mind when you design its
view page. The page should be simple and straightforward, either presenting a single kind of information, or allowing
the user to make a straightforward request.
A portlet view page should make sure to use all the space made available to it. If the portlet consists of a
<wdk:table><wdk:table> widget and its contents, make sure that the width of the child elements adds up to 100% (and
make sure to specify all the widths as percentages, not in pixel sizes).
If the view is not a single table widget, then you should put all the view's contents into an HTML <table> node, and
make sure that the node's width and height are both set to 100%. Thus, a portlet view page has the following structure:

<xsl:template match="wdk:model">

<table width="100%" height="100%" border="0"

cellspacing="1" cellpadding="0">

<!-- content of the portlet goes here -->

</table>

</xsl:template>
Again, the height and width should be specified as percentages, not as pixels.

| Designing Portlets | 296

Portlet Model Files


As noted above, a portlet model file is like an application page's model file. There are no special requirements for
command objects used by portlet model files.

Links from Portlet Pages


If a portlet opens another page with a <wdk:link><wdk:link> widget, Saba has to decide whether the linked page should
replace the portlet, or the entire portal page. It makes the decision this way:

If the link's <action> node (see page 4-83) is of type portal, Saba repaints this portlet, refreshing its contents.
Otherwise, the page called replaces the entire portal page.

Of course, you can always have the link open a popup window by giving it a <popup> child.

Testing a Portlet Page


To test a portlet page, change the control file's cocoon-process directive's type from portlet to wdk, and load
it the way you would any WDK page.Once you have verified the page's contents, change the type back to portlet.

Coding a Portlet Manager


Each portlet has a portlet manager object. The object provides information to Saba about the portlet, including the page
it opens, the parameters it is passed, and any help it might have. The manager provides several methods to Saba (for
example, getParameterDetails(), which reports what parameters the portlet expects). When you code the class,
make sure those methods return appropriate values.
We recommend that you write your portlet manager by extending the AbstractPortletPageManager class. This
method provides appropriate versions of all the methods Saba requires. When you extend this class, add a no-argument
init() method. The method initializes several object fields, by making calls to special-purpose methods. Once you
have initialized the object, you can rely on the methods it inherits from AbstractPortletPageManager to provide
all the appropriate information to Saba.
As always, you can find the precise details of all methods by consulting the class's reference page.
Note: In principle, you could create a portlet manager from scratch, writing your own code which implements the
PortletPageManager interface. However, we recommend that you create your portlet managers by extending
AbstractPortletPageManager, providing an init() method.

Initializing the Portlet Manager


As noted, the AbstractPortletPageManager object contains all the methods required by Saba. However, the
abstract class cannot, obviously, know the specific information for your portlet! It can provide to Saba a getPage
method, which lets Saba find out what the portlet page is. However, be sure to provide this information to the portlet
page manager, so it can report it to Saba.
To do this, provide an initialization method, init() (with no arguments). This method is declared as an abstract method
in AbstractPortletPageManager. The method must make calls to various special-purpose
AbstractPortletPageManager methods, specifying the portlet's specific information.

| Designing Portlets | 297

For example, every portlet has a particular page (a WDK application page, CGI script, etc.). If you wanted to define a
simple portlet with no parameters or online help, you could do it by creating a new portlet manager class like this:

public class MySimplePortletManager

extends AbstractPortletPageManager {

// Only one method is needed, "init"

public void init() throws SabaException {

/* Register the portlet's WDK page */

registerPage(PortletPageManager.kDefaultDisplay,

"/platform/presentation/portal/portlet/my_simple_portlet.rdf");

} //init

} //class
Saba can now create the object and query it to find out what the portlet's page is.
There are several things you can specify when you create a portlet:

The portlet's page (this is required)


Portlet parameters (this is required if the portlet has required parameters)
An online help page (optional)

Each is described in its own section.

Registering a Portlet's Page


When you initialize your portlet page manager, you must specify the page for the portlet. You do this by calling the
method registerPage. This method is passed two parameters:

The request which should return this page. In the future, we may support having portlet managers receive several
different requests, and being able to return a different page for each request. For this reason, specify which requests
should return this page. Currently, the only supported value is PortletPageManager.kDefaultDisplay.
The URL of the page associated with that request. This must be the absolute path to a file on this or another host. If
the portlet is a WDK page, this should be the path of the page's control file (.rdf).

| Designing Portlets | 298

For example, if a portlet opens the WDK page


/platform/presentation/portal/portlet/MyCorpPortlet.rdf, you would put this call in the init
method:

registerPage(kDefaultDisplay,

"/platform/presentation/portal/portlet/MyCorpPortlet.rdf");
If the portlet opens an xHTML file named http://portlets.mycorp.com/portlets/stockTicker.htm,
you would make this call:

registerPage(kDefaultDisplay,

"http://portlets.mycorp.com/portlets/stockTicker.htm");

Registering Required Parameters


Like any WDK page, a portlet can have required and optional parameters. You must register all required parameters
through the portlet manager. (You should not register any optional input parameters.)
To register the required parameters, call the registerParameter method. This method is passed a
ParameterDetail object, which describes the parameter being registered. When a user adds the portlet to a portal,
the user has to enter an appropriate value for each parameter. (For example, you might write a stock ticker portlet with
a stock-symbol parameter. The user would have to enter a stock symbol when he or she added the ticker to a portal.)
For example, to register a string parameter named url, you might put this in your init method:

ParameterDetail pd = new ParameterDetail("url",


TypeInfo.createStringType());registerParameter(pd);
For full details on this method, as well as the TypeInfo and ParameterDetail objects, see the Java reference
pages.

Registering Help Pages


If you wish, you can create an online help page for your portlet. If you register a help URL, Saba displays an appropriate
help button on the portlet. Users can click that button to launch the help.
This is entirely optional. If you do not register a help page, Saba does not display a help button.

| Designing Portlets | 299

To add a help page, call the registerHelpURL method, and pass the URL of the help page. This must be passed as
an absolute path to a file either on that server or on another. For example, to register the help page (on the Saba server)
"/portlet_help/myPortletHelp.html", you would include this call in your init method:

registerHelpURL("/portlet_help/myPortletHelp.html");
To use a help page on another server, such as
"http://portlets.mycorp.com/portlet_help/myPortletHelp.html", you would include this call:

registerHelpURL( "http://portlets.mycorp.com/portlet_help/myPortletHelp.html");

Installing a Portlet
To install a portlet, follow these steps:

Make sure the portlet page manager is in Saba's Java class path.
Install the portlet page in the appropriate location (wherever you specified in the portlet page manager code).
Launch the Saba application's Create Portlet screen, as described in the System Administrator Guide. Follow the
instructions there on creating portlets.

Chapter

9
Writing Web Services
Topics:

Overview
Command Object Requirements
Generating a WSDL File
Installing a Custom Web Service

This chapter describes how to write a web service that publishes Saba information
through a SOAP connection.

| Writing Web Services | 302

Overview
Saba Cloud provides several web services that enable remote clients to read and alter Saba's data store through a SOAP
connection. The system ships with several web services installed. In addition, you can write additional web services,
providing any other data or features you need.
You create a new web service by writing a Saba command. Command objects are usually used with Saba application
pages; a command object can return XML data to be displayed by an application page, and can also make changes to
the data store or produce any other side-effects you need. By registering a command object as a Saba web service, you
can make the same functionality available to remote clients.
To create a new web service, follow these steps:
1. Write a command object which performs the desired functions. The object might retrieve a particular set of data,
modify the data store (for example, by creating or modifying an object), produce some other side-effect, or any
combination of the three. Compile the object and install it in the application server's class path.
2. If you want the web service to be available to through a SOAP connection, use the provided generateWSDL script
to create a WSDL (Web Services Description Language) file from the command object. Place the WSDL file on a
web server accessible to your clients (you can put it on the server you use to host Saba Cloud).
Note: Clients that use Saba's Java or C# interfaces do not need a WSDL file, though you may wish to create
one anyway to help document the web service.
3. Register the command object as a Saba web service by using the System Administration module's New Web Service
screen. If you want to provide a WSDL file, provide its URL here.
After you do this, clients can access your web service the same way they would any other Saba web service. For more
information about using web services, see the Web Services Developer Guide.
For full details about writing command objects, see Chapter 6, "Coding Customization (Java)"Chapter 6, "Coding
Customization (Java)".

Command Object Requirements


The core of a web service is the command object. Command objects interact with the Saba data store, performing any
required actions, and output an XML document. When the command object is used by an application page, the XML
output is displayed by that page. When the command object is used to provide a web service, the XML output is delivered
through a SOAP connection to the web client.
If a command object is used to provide a web service, it must meet the following requirements:

The class must extend com.saba.web.dk.SabaWebCommand


The command must declare all its parameters by using the addInParam() method in its constructor
The command must use the getArg() method to retrieve parameter values
The command must not attempt to access the HttpServletRequest parameter
The command object must not use out parameters. Instead, it should serialize its return data with the IXMLVisitor
parameter
Note: These restrictions are imposed because a web service may be invoked through protocols other than HTTP.
For example, a command object which was invoked by an application page could examine the HTTP request
to find the parameters; a web service command object which was invoked by a SOAP connection could not do
this. However, there is no reason why the same command object could not be used for both a web service and
an application page, provided it meets the above restrictions.

| Writing Web Services | 303

For example, here is a simple web service command object which receives a single parameter, and returns that same
parameter serialized in an XML node:

/**

* EchoCommand is a simple command that echos the input.

*/

public class EchoCommand extends SabaWebCommand

public EchoCommand() throws SabaException {

super();

addInParam("echo", String.class, "String to echo");

/**

* Serialize results.

* This command serializes results in the following format:

<echo>input string</echo>

* @param request

| Writing Web Services | 304

* @param visitor

*/

public void doExecute(HttpServletRequest request, IXMLVisitor visitor)

throws Exception

String echo = (String) getArg("input");

visitor.visit(null, "echo", echo);

} /* doExecute */

} /* EchoCommand */

Generating a WSDL File


If you are writing a client web application in Java or C#, you can use the Saba InfoServiceClient interface to
access your custom web services. This interface does not need an WSDL file. Thus, if all the clients are using this
interface, you do not need to create or install a WSDL file for your new service. However, if your clients are using the
SOAP protocol directly, consult theWSDL file of the web service for descriptions of the methods in the API of the web
service.
Saba provides a utility for generating WSDL files from command objects. This utility, generateWSDL, is installed
into the Saba installation's bin directory. (The Windows version is named generateWSDL.bat. The Unix version
is generateWSDL.sh.) To generate the WSDL file, execute the command this way:

java2WSDL.bat command_class service_name


Table A-1:) Parameters for java2wsdl
Parameter

Description

command_class

Fully-qualified class name of the command object which is used by the webservice. This object
must be in your Java class path

| Writing Web Services | 305

Parameter

Description

service_name

The name you use for the web service. Enter the same name when you register the service
through the System Administration module. The WSDL file generated is given the name
service_name.wsdl.

You must invoke this utility on the machine where Saba is installed (and where the class has been installed into your
application server's class path), and Saba must be running at the time. The utility prompts you to enter a user ID and
password for a Saba user account. It then prompts you to enter appropriate parameters for the command object. The
utility invokes the command object with those parameters, analyzes the XML output, and generates a WSDL file based
on that output. The file is written to the current directory, with the name service_name.wsdl (using whatever service
name you passed to java2WSDL.).
For example, if you want to use the class com.mycorp.myCommand to provide a service named myService, you
would invoke the command this way:

generateWSDL.bat com.mycorp.myCommand myService


(Of course, on a Unix machine you would use the script generateWSDL.sh.) The WSDL file would be written to
the current directory, with the name myService.wsdl.
Once the WSDL file has been output, you should edit it to improve its style and usability. Some things you should
consider include:

All parameters are defined in an xsd:sequence. That is, they are assumed to all be required. If any of the parameters
are optional, you must change the WSDL file to reflect this..
You may wish to replace some xsd:string elements with enumerated types (specified with xs:enumeration).
Commands that may generate different XML formats depending on their input values have only one possible output
captured in the generated schema (that is, whatever output format was produced by the parameters you provided to
java2WSDL). In that case, add the other possible output formats by hand. (You may wish to run java2WSDL
several times, with the different types of input values, then manually combine the WSDL files to produce a single
WSDL file which allows for any of the possible output formats.)
All attributes are given a datatype of xsd:string. You may be able to change these to more accurately reflect the
data types used.
You may wish to explicitly define xsd:ID types.
You may wish to add explicit minOccurs and maxOccurs attributes.
All schema definitions are generated with the default namespace. You can add namespaces explicitly.
Generated schemas are completely unmodular and extremely nested. You should consider extracting common
elements.
You may wish to change the target namespace.
The generated WSDL file does not have any comments. You should add documentation to make the WSDL file
easier to use.

| Writing Web Services | 306

For example, here is a fragment of the schema created by generateWSDL for the learner profile web service:

<xsd:element name="certification">

<xsd:complexType>

<xsd:sequence>

<xsd:element name="id" type="xsd:string"/>

<xsd:element name="name" type="xsd:string"/>

<xsd:element name="targetDate" type="xsd:string"/>

<xsd:element name="status" type="xsd:string"/>

</xsd:sequence>

</xsd:complexType>

</xsd:element>
and here is the equivalent fragment from the manually edited schema definition for this service:

<xsd:complexType name="certification">

<xsd:sequence>

<xsd:element name="id" type="saba:idType"/>

<xsd:element name="name" type="xsd:string"/>

<xsd:element name="startedOn" type="xsd:date"/>

<xsd:element name="targetDate" minOccurs="0" type="xsd:date"/>

| Writing Web Services | 307

<xsd:element name="acquiredOn" minOccurs="0" type="xsd:date"/>

<xsd:element name="status">

<xsd:simpleType>

<xsd:restriction base="xsd:string">

<xsd:enumeration value="Acquired"/>

<xsd:enumeration value="In Progress"/>

<xsd:enumeration value="Expired"/>

<xsd:enumeration value="Discontinued"/>

</xsd:restriction>

</xsd:simpleType>

</xsd:element>

</xsd:sequence>

</xsd:complexType>
Notice that the following changes were made:

The element definition was extracted into a top-level complexType definition to make it more modular
The type of id and targetDate was made more specific
The type of status was changed to a set of enumerated values
The schema was augmented with elements for startedOn and acquired. This is data that is potentially serialized
by the command, but happened to not be present in the example data provided for this run of the command.

| Writing Web Services | 308

Installing a Custom Web Service


To make a web service available to client programs, use the System AdministrationuGeneral ConfigurationuWeb
ServicesuAdminister Web Services screen. Click the New Web Service link to add a web service. You are prompted
for the following information:
Table A-1:) Web Service Data Fields
Field

Required?

Description

Name

Yes

Name of the web service. Java clients must pass this name to the
InfoServiceClient.callService method to access the
service.

Description

No

A text description of the service.

Java Class

Yes

The fully-qualified class name of the comand object which provides


the service. This class must be in the application server's class path.

Abstract WSDL Binding

No

URL of the WSDL file describing the service. This is not needed if
all clients use the Java InfoServiceClient interface.

Click Save to make the web service available for clients.

Chapter

10
Localizable Objects
The following table provides a complete list of all localizable objects in the
Saba application, including the localizable attributes of each object:
Table B-3:) Localizable Objects (page 1 of 4)
Object

Localizable Attributes

Class ID

Product Group

Name

ddcls000000000001037

Offering Template

Title
Description
Abstract

Delivery Mode

Description

ddcls000000000001404

Delivery Type

Name

ddcls000000000001056

Description

Virtual Classroom

Abstract

ddcl

Description

Instructor-led Offering

Abstract

ddcls000000000001039

Description

WBT

Abstract

ddcls000000000001420

Description

Remote Lab

Abstract
Description

ddcls000000000001502

| Localizable Objects | 310

Object

Localizable Attributes

Class ID

Video On Demand

Abstract

ddcls000000000001444

Description

Simulation

Abstract

ddcls000000000001483

Description

Job Type

Name

ddcls000000000001072

Description

Role

Name

ddcls000000000001104

Description

Equipment

Name

ddcls000000000001051

Resource Inventory

Name

ddcls000000000001073

Description

Competency

Name

ddcls000000000001350

Description

Competency Levels

Name

ddcls000000000001351

Description

Competency Groups

Name

ddcls000000000001353

Description

Competency Criticality

Name

ddcls000000000001072

Description

Competency Evidence

Name

ddcls000000000041351

Description

Competency Method

Name

ddcls000000000041352

Model Group

Name

ddcls000000000001373

| Localizable Objects | 311

Object

Localizable Attributes

Class ID

Goal Metrics

Name

ddcls000000000006004

Description

Goal Types

Name

ddcls000000000006001

Description

Goal Actions

Name

ddcls000000000006012

Description

Goal Unit

Name

ddcls000000000006003

Description

Goal Status

Name

ddcls000000000006000

Description

Performance Method

Name

ddcls000000000001520

Description

Provider Type

Name

List of Values (LOV)

Description

Notification Event

Name

ddcls000000000001517

ddcls000000000001085

Description

UI Group

Name

ddcls000000000001361

Folder

Name

ddcls000000000001471

Certification

Name

ddcls000000000001330

Description

Certification Path

Name

ddcls000000000001378

Certification Module

Name

ddcls000000000001363

| Localizable Objects | 312

Object

Localizable Attributes

Class ID

Curriculum

Name

ddcls000000000001330

Description

Curriculum Path

Name

ddcls000000000001378

Report Category

Name

ddcls000000000001481

Description

Chapter

11
WDK Page Tutorial
Topics:

Overview
Writing a Simple Portlet
Using Widgets
Command Objects and Visitors

This chapter describes how to write a WDK application page. The chapter covers
a wide range of possible page types, from the very simple to the elaborate.

| WDK Page Tutorial | 314

Overview
Saba application pages are written in our WDK format. This format is described in detail in Chapter 3, "Scripting
Customization (WDK)"Chapter 3, "Scripting Customization (WDK)". A WDK application page is made of three files:

The control file is the master file. It specifies the other two files used by the application page, assigns a unique
identifier number to the page, and specifies certain other high-level resources (such as the widget library used by
the page).
The model file specifies the data displayed in the application page, and also specifies which widgets are available to
the page.
The view file specifies how the data and widgets are arranged on the application page.

These files are all written in XML. The control file uniquely identifies the application page. That is, by specifying a
control file, you have specified an application page. By contrast, a single model file can be used by several different
application pages. For example, a single model file can present information about users, and several different view files
can present the model file's data in different ways. For example, one view file can display a table with all the data,
another can display only certain columns of the table, and another can display a stripped-down version suitable for
displaying in a portlet.
It is possible to use a single view file with several different model files, but this is far less common than using several
views with a single model. Usually, a view file is tailored to a specific model file.

Application Page Life Cycle


The life-cycle of a Saba application page is more complex than that of an ordinary web page, and it affects the way Saba
pages are written and maintained.
Each of the three files that make up a Saba application page serves a different function and is used at a different time.

The model file is compiled to create a Java class. This compilation is done once for each model file. Once the Java
class is created, it can be used every time a user opens an application page which uses that file. The compiled Java
class generates XML output, which contains the data displayed on an application page.
The view file is used to transform the XML output into the web page that is displayed by a user's brower. It produces
a web page with XHTML and Javascript. There is not usually any Java in the output, so the user's browser does not
need to be Java-enabled. (Of course, you could write an application page which runs a Java applet if you wished.)
The control file identifies the model file and view file used by the application page. When you want to add an
application page into the system, you do it by creating a link (or a menu item) which opens that control file; Saba
then uses the control file to open the view and model file.

Thus, when a user launches an application page, the system follows this sequence of events:
1. The system checks the control file to see what model file and view file are used by the application page.
2. The system checks to see if the model file has already been compiled into a Java class. If it has not, the system
compiles that model file.
3. The system creates a Java object from that class, and uses the object to generate the XML data used by the application
page.
4. The system uses the view file (and any associated stylesheets, like the widget definition stylesheets) to transform
the XML data into an XHTML/Javascript file.
5. The system sends that file to the user's browser, which displays the finished application page.
If the application page is a portal, the system also follows this sequence for each portlet on the page.
The way Saba constructs the finished application page has a few implications you should keep in mind:

The first time a person runs an application page, Saba has to compile the model file into a Java class. This greatly
slows down the application's response time for that page. The next time anyone views that page (even with different

| WDK Page Tutorial | 315

data), Saba can use the cached Java class, greatly improving response speed. (You can also use a Saba utility to
compile all the application pages at once, instead of waiting for users to load the pages.)
When a user views an application page, Saba checks to make sure that the compiled model class is up-to-date. If the
model file is more recent than the Java class, Saba recompiles the class. However, some application servers keep
caches of Java classes used. As a result, if you change a model file while Saba is running, and the model file has
already been used since the last time the application server was launched, the server may use its cached version of
the class instead of the recompiled version based on the new model file. This keeps the changes from showing up
in the compiled application page. For that reason, after you make changes to model files, you may need to restart
your application server to view the changes. (This is not necessary if you make changes to the view or control file.)

Working with WDK Pages


There are two common situations in which one might edit a WDK page.

You might create a brand-new page entirely from scratch. In that situation, you would need to create a control, model,
and view file, and insert the page into the application.
You might edit an existing WDK page. In that case, you might duplicate an existing page, modify the duplicate, and
insert the new page into the application. You could also edit the existing page in place.

If you are editing an existing application page, you can ignore much or all of what you see in the files and make a small
alterations. For example, you might decide that a particular table should show less information than it does. You could
do this by editing the view file to remove a particular column, and not altering the model or control files at all. On the
other hand, if you are creating a new page, you must understand how all three files are constructed, but you might not
need to know subtler details about how the components work. The page that you construct can be simpler than typical
Saba application pages.
Of course, there isn't always a clear line between creating new pages and editing existing ones. Usually when you create
a brand new application page, do it in stages, gradually building up the functionality from one draft of the page to the
next (and borrowing code from existing application pages that do similar things). That is how this tutorial is structured.
It begins by describing how to create a simple portlet, then describes how to add functionality to the portlet, turn it into
an application page, and then add advanced features.

Tutorial Description
This tutorial describes how to create a wide number of WDK pages. It shows how to build simple application pages
from scratch, and also how to modify simple pages to create more complex ones.
Note: You may never need to create an entire application page. Usually, you are able to design web pages by
modifying existing WDK application pages.
The tutorial covers the following steps:

Writing a simple portlet


Retrieving internationalizable text from Java resources
Using widgets in application pages
Using a command object to construct XML data

Writing a Simple Portlet


This section describes how to write a simple Saba portlet. A portlet is a special kind of application page that is designed
to be displayed on a portal page along with other portlets. It is written the same way as any other application page, but
with special restrictions. These are fully detailed in Appendix A, "Designing Portlets"Appendix A, "Designing Portlets",

| WDK Page Tutorial | 316

but portlets should be small, simple, and not make assumptions about how much screen space they can have. This ensures
that Saba can resize the portlet as necessary, and still have it be readable when displayed.
Because a portlet is small and self-contained, and because it is easy to install into Saba, portlets are a very useful way
to design and test WDK functionality. For example, very often the meat of an application page is a table displaying
information. You can build and test that table in a portlet. Then, once that code is working, you can expand it into a full
application page.
This section covers the following steps:
1. Writing a portlet that displays simple text, installing it in Saba, and viewing it.
2. Expanding the portlet to use some common widgets, and to follow Saba's best practices.

First Portlet: "Hello World"


This section describes how to write a simple portlet which displays static text. Needless to say, there are simpler ways
to do this than writing a Saba application page. If you want to write a "message of the day" portlet, you could write it
as an XHTML page. This section describes how to write it as an application page so you can use it as a framework for
more elaborate pages.
Like any other application page, a portlet is made up of three files: a model file containing the data, a view file which
presents the data for display, and a control file which specifies the model and view files, along with some other high-level
information. In addition, a portlet requires a special-purpose Java object. For each portlet, you must create and install a
class based on the AbstractPortletPageManager class. This object is used to inform the system where the
control file for the portlet is located. The system can then examine the control file to find the model and view files. (With
a regular application page, the control file is linked to some other way-either through a link widget on some other page,
or by being added to the menu system.)
This section lists typical control, application, and view files, as well as a portlet page manager object which loads the
portlet. The control, model, and view files can be installed anywhere inside the Saba application's document root. In
this example, as is usually the case, the three files are all installed in the same directory. The portlet page manager object
must be compiled, and the class file installed anywhere in the application's Java path.

| WDK Page Tutorial | 317

"Hello World" Control File


The control file specifies the view and model files used by the portlet. Control files vary little from one application page
to the next.

<?xml version="1.0" encoding="UTF-8"?>

<?cocoon-process type="portlet"?>

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:wdk="http://www.saba.com/XML/WDK">

<!-- Saba reserves IDs up to 500000 -->

<rdf:Description id="511111">

<rdf:type resource="http://www.saba.com/XML/WDK/Control"/>

<wdk:version>1.0</wdk:version> <wdk:model
rdf:resource="helloWorldPortlet.xml"/> <wdk:view
rdf:resource="helloWorldPortlet.xsl"/> <wdk:widgets
rdf:resource="/wdk/xsl/widget/wdk_widgets.xsl"/>

</rdf:Description>

</rdf:RDF>
The bold-face sections are areas you usually would need to change when you create a new control file. In addition, if
the model file contains links to model files (including itself), include a <wdk:links> node, as described in
"<wdk:links>""<wdk:links>" on page 3-19. Other than that, your various conrol files can all look pretty much the same.
The parts of the control file that might change include:

Cocoon process type: The <?cocoon-process> tag must have a type attribute. This attribute is set to portlet
for portlet pages, and wdk for ordinary application pages.
ID Number: Every application page (including portlet pages) must have a unique ID number. Saba reserves all ID
numbers up to 500,000. You can use any integer above that for your application pages. The ID number is specified
in the <rdf:description> node's id attribute.
Model and view pages: The control file specifies the model file and the view file used by this page or portlet. This
is specified either with the full URL of the model and view files, or (more often) with the path relative to the location
of the control file. Thus, if the model, view, and control files are all in the same directory (as is usually the case),
you can enter the names of the files.

| WDK Page Tutorial | 318

"Hello World" Model File


The model file contains all the data displayed by the application page or portlet. As noted above, Saba compiles the
model file to create a Java class. It then uses that Java class to generate the XML data for the application page.
In this case, the only data displayed by the page is a simple line of text. To begin with, we hard?code that text into the
portlet. However, as you'll see later in the tutorial, that is not the ideal way to display text in an application page. It is
used here as an example.

<?xml version="1.0"?>

<xsp:page language="java"

| WDK Page Tutorial | 319

xmlns:xsp="http://www.apache.org/1999/XSP/Core"
xmlns:wdktags="http://www.saba.com/XML/WDK/taglib">

<xsp:structure>

<xsp:include>com.saba.*</xsp:include>

</xsp:structure>

<wdk:page xmlns:wdk="http://www.saba.com/XML/WDK">

<!-- the head element contains standard header data


for all WDK model files -->

<wdk:head>

<!-- No input or output parameters for this portlet. -->

<!-- the title of the page or portlet -->

<wdk:title>Hello World</wdk:title>

<!-- WDK labels, usable by the view file -->

<wdk:labels>
</wdk:labels>

<wdk:label name="hello_text">Hello, world!</wdk:label>

</wdk:head>

<wdk:form method="POST">

<!-- Model information is in this tag. However, this portlet has no


information here.-->

<wdk:model>

| WDK Page Tutorial | 320

<!-- No model information needed. -->

</wdk:model>

</wdk:form>

<!-- Widgets are defined here. -->

<wdk:widgets>

<!-- No widgets in this portlet. -->

</wdk:widgets>

</wdk:page>

</xsp:page>
As you can see, this model file contains very little information. The only information specific to the portlet is the text
associated with the hello_text label, "Hello world!"

"Hello, World" View File


The view file is an XSLT stylesheet that transforms the data in the model file into a viewable Saba application page or
portlet. In this case, the view file can be very simple. It displays the text of the model file's hello_text variable in
an XHTML page.

<?xml version="1.0" encoding="UTF-8"?>

<?wdklint ignoreRules="XSL_HTML"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"


xmlns:wdk="http://www.saba.com/XML/WDK"

| WDK Page Tutorial | 321

xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rss10="http://purl.org/rss/1.0/">

<xsl:import
href="/wdk/xsl/view/wdk_defaultview.xsl"/>

<xsl:import
href="/wdk/xsl/view/wdk_widgetlayout.xsl"/>

<xsl:template match="wdk:model">

<!-- Portlet contents should be put inside an HTML table


or a table widget. --><table width="100%" height="100%" border="0"
cellspacing="1" cellpadding="0">
<tr><td><xsl:value-of
select= "$wdkLabel[@name='hello_text']"/></td></tr> </table>

</xsl:template>

</xsl:stylesheet>
The defaultview.xsl file is responsible for creating the general framework of the portlet, including an <HTML>
node. To fill in the contents of the <HTML> node, that stylesheet invokes the wdk:model template which you write.
The result is a portlet page containing a single <p> node, which contains the value of the hello_text variable:

<HTML>

<table width="100%" height="100%" border="0"

cellspacing="1" cellpadding="0">

<tr><td>Hello, world!</td></tr>

</table>

</HTML>
Note: As noted in Appendix A, "Designing Portlets"Appendix A, "Designing Portlets", a portlet should consist of
either an HTML table (and its contents), or a Saba table widget. Thus, the "Hello, world!" text is put inside an
HTML table with one row and one column.

| WDK Page Tutorial | 322

"Hello, World" Page Manager Object


In order for the application to load the portlet, it must know where the page files are installed. The application finds this
out by instantiating a page manager object specified for the portlet. That object, in turn, registers the control file with
the system.

| WDK Page Tutorial | 323

When you write the portlet page manager, you must know where the application page's control file is installed. (The
Saba application can then examine the control file to find the locations of the model and view files.) In this case, we
assume that the control file is installed as /tutorial/portlet/helloWorldPortlet.rdf.

package com.saba.client.portal;

import com.saba.exception.SabaException;

import com.saba.portal.AbstractPortletPageManager;

import com.saba.portal.PortletPageManager;

/**

* This class registers the page for the Hello World portlet.

*/

public class HelloWorldPortletPageManager extends AbstractPortletPageManager


implements PortletPageManager

/** The constructor method doesn't need to do anything...*/

public HelloWorldPortletPageManager() {

/**

* Initialize the portlet. You must register the portlet by calling

| WDK Page Tutorial | 324

* registerPage.

*/

public void init() throws SabaException {

/* register the control page for this portlet */

registerPage(PortletPageManager.kDefaultDisplay,
"/tutorial/portlet/helloWorldPortlet.rdf");

} /* init */

} /* HelloWorldPortletPageManager */
After you compile this class, install it into Saba. Place the class file in the Saba application server's Java class path, then
add the portlet and its manager using the Saba system administration module (as described in the System Administrator
Guide).

Following Best Practices: An Improved "Hello World"


The simple portlet described above shows how to display text, or indeed, to display any xHTML code. However, it does
not follow the WDK's best practices.
As a general rule, you should not hard-code text in your application pages. In the case of the above portlet, the text
"Hello, world!" is coded directly into the application page's model file. This makes it difficult to internationalize the
portlet if you are providing it to locales which use a different language. Instead of hardcoding text into your application
page, all the text on an application page should be loaded either from internationalizable resources, or from the Saba
application database.
Note: Saba's WDK Eclipse Plug-in provides a feature for editing resource labels, as long as they are named
according to Saba's convention. This tutorial follows that naming convention. In addition, you can use the Eclipse
Plug-in to create the custom labels, and the resource file for them. For more information, see Appendix H, "Eclipse
Plug-in"Appendix H, "Eclipse Plug-in".
Create a normal Java resource bundle for the page's labels. If you wish, this resource bundle can contain versions of the
resources for various locales, or a default version of the resources. Install the resource bundle into the Saba application
server's load path.

| WDK Page Tutorial | 325

Create appropriate resource entries in that resource bundle. In this case, we assume that the resource bundle is named
"my_custom_pages", and we add a single entry to the default resource file:

kI18n511111helloWorldLabel=Hello, world!
Note the naming convention followed. All of an application page's resources should be given names that fit the pattern

kI18n<page_ID_number><label_name>
The model file can then load any resources it needs from that bundle. To use the value of that label, you would change
the model file described above to the following. (Once again, changes are in bold-face.)

<?xml version="1.0"?>

<xsp:page language="java"
xmlns:xsp="http://www.apache.org/1999/XSP/Core"
xmlns:wdktags="http://www.saba.com/XML/WDK/taglib">

<xsp:structure>

<xsp:include>com.saba.*</xsp:include>

</xsp:structure>

<wdk:page xmlns:wdk="http://www.saba.com/XML/WDK">

<!-- the head element contains standard header data


for all WDK model files -->

<wdk:head>

<!-- No input or output parameters for this portlet. -->

<!-- the title of the page or portlet -->

<wdk:title>Hello World</wdk:title>

<!-- Load the resource bundle... -->

| WDK Page Tutorial | 326

<wdktags:i18n.load resource="my_custom_pages"/>
by the view file -->

<!-- WDK labels, usable

<wdk:labels>

<wdk:label name="hello_text"><wdktags:i18n.label
name="kI18n511111helloWorldLabel"/></wdk:label>

</wdk:labels>

</wdk:head>

<wdk:form method="POST">

<!-- Model information is in this tag. However, this portlet has no


information here.-->

<wdk:model>

<!-- No model information needed. -->

</wdk:model>

</wdk:form>

<!-- Widgets are defined here. -->

<wdk:widgets>

<!-- No widgets in this portlet. -->

</wdk:widgets>

</wdk:page>

| WDK Page Tutorial | 327

</xsp:page>
When Saba compiles the Java code for the model page, it transforms the <wdktags:i18n.label> node into an
appropriate command to load the text from the resource bundle. Thus, the page uses whatever label is best suited for
the current locale.
The view and control files do not need to be changed to make use of the new label. The view file loads the contents of
the hello_text XML label, as before.

Using Widgets
The previous example showed how to create an application page or portlet which uses straight xHTML. You could use
exactly the same technique to display any other code which is supported by your target browser, such as Javascript or
Java applets.
Saba also provides a powerful library of widgets which you can use in your pages and portlets. There are several
advantages to using widgets:

The widgets provide a wide range of functionality, including special tools for accessing the Saba data store.
The Saba application automatically adjusts the widgets to match whatever theme and skin is in use.
Saba provides a syntax for dynamically creating as many widgets as necessary for the data you want to display. For
example, you might create a page which displays all students in a class, along with a "delete student" button for each
student. In that case, you could define the "delete student" widget once, then use WDK directives to instruct Saba
to create a copy of that widget for each row in the "students enrolled" table.

This section of the tutorial describes how to add a widget to the "Hello World" portlet defined in the previous section.
In this case, the portlet has a single widget, a textArea widget which can be used to display or input text. The following
section describes how to create multiple widgets dynamically to match whatever text your application page may be
displaying.
If you use a widget, you must insert code in two places:

The model file defines the widget itself, specifying what parameters are passed to the widget and how it should be
displayed.
The view file specifies where in the file the widget should be displayed.

Model File with Widget


This version of the "Hello, World" portlet displays the text with an input widget. This widget shows a text entry field,
with whatever label you define. The portlet initializes the field with the "Hello, world" message and marks the widget
as view-only.
Widgets are defined in the <wdk:widgets> area of the model file. This version of the model file is exactly like the
one in the previous section, except for that node. (Once again, we assume that the resource bundle and label specified
evaluate to some appropriate text, like "Hello, world!")

<?xml version="1.0"?>

<xsp:page language="java"

| WDK Page Tutorial | 328

xmlns:xsp="http://www.apache.org/1999/XSP/Core"
xmlns:wdktags="http://www.saba.com/XML/WDK/taglib">

<xsp:structure>

<xsp:include>com.saba.*</xsp:include>

</xsp:structure>

<wdk:page xmlns:wdk="http://www.saba.com/XML/WDK">

<!-- the head element contains standard header data


for all WDK model files -->

<wdk:head>

<!-- No input or output parameters for this portlet. -->

<!-- the title of the page or portlet -->

<wdk:title>Hello World</wdk:title>

<!-- Load the resource bundle... -->

<wdktags:i18n.load resource="my_custom_pages"/>

<!-- WDK labels, usable by the view file -->

<wdk:labels>
<!-- Label is no longer needed! -->

</wdk:head>

<wdk:form method="POST">

</wdk:labels>

| WDK Page Tutorial | 329

<!-- Model information is in this tag. However, this portlet has no


information here.-->

<wdk:model>

<!-- No model information needed. -->

</wdk:model>

</wdk:form>

<!-- Widgets are defined here. -->

<wdk:widgets>

<wdk:input name="helloText">

name="kI18n511111helloWorldLabel"/></value>
</wdk:input> </wdk:widgets>

<value><wdktags:i18n.label
<viewOnly>true</viewOnly>

</wdk:page>

</xsp:page>
As you can see, this version of the portlet no longer has a label. The view file doesn't display the text directly, by accessing
the label. Instead, it displays the helloTextwidget, which contains the message.

View File with Widget


In the earlier version of the "Hello World" portlet, the view file retrieved the text string from the model file, then displayed
that text as HTML. In the new version of the portlet, the view file does not access the text directly. Instead, it contains
a directive to display the widget which contains the text.

<?xml version="1.0" encoding="UTF-8"?>

<?wdklint ignoreRules="XSL_HTML"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"


xmlns:wdk="http://www.saba.com/XML/WDK"

| WDK Page Tutorial | 330

xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rss10="http://purl.org/rss/1.0/">

<xsl:import
href="/wdk/xsl/view/wdk_defaultview.xsl"/>

<xsl:import
href="/wdk/xsl/view/wdk_widgetlayout.xsl"/>

<xsl:template match="wdk:model">

<!-- Portlet contents should be put inside an HTML table


or a table widget. --><table width="100%" height="100%" border="0"
cellspacing="1" cellpadding="0"><tr><td>
<xsl:apply-templates
select="$wdkWidget[@name='helloText']"/>
</td></tr>

</table>

</xsl:template>

</xsl:stylesheet>
The bold-faced statement applies the standard Saba widget library's stylesheet to the selected widget. In this case, the
widget is identified by its name, but you could identify the widget by any other XSLT path. For example, you could
search for a widget with a particular ID, or for a widget that's the child of some other node.

Command Objects and Visitors


Unlike application pages that display static content, dynamic pages display information retrieved from the Saba data
store. In some circumstances, you do not even know how much data the page displays. For example, a page written to
display all the goals assigned to students in a class displays no goals if the class has no students or the students have no
goals. If the students have goals, the page can display pages of information.
The WDK enables you to write application pages that dynamically generate their content by executing Java code that
invokes a command object.
A model file is not transformed directly into the application page that users see. Instead, the model file is compiled once
into a special Java class. Then, whenever a user views that application page or portlet, the Java object is created, and it
generates an XML document. The view file, in turn, transforms that created XML document into the application page
or portlet that is shown to the user.
A model file can contain one or more <wdktags:execute> tags. When the model file is compiled, these tags are
turned into Java code that creates a specified Java object, and invokes that object's execute method. That method is
responsible for returning a well-formed XML node. When the model file's object is executed, the <wdktags:execute>
directive is replaced by whatever code is output by the command object.

| WDK Page Tutorial | 331

This example uses a simple command object to generate the XML node. In this example, the command object generates
a static, fixed node, containing data which is displayed in the application page. Of course, the true strength of command
objects is that they can generate their XML output dynamically, and can retrieve information from the Saba data store.
In addition, this application page is designed as a stand-alone page, instead of as a portlet. There is very little difference
between writing a portlet and writing an application page. However, you must add the application page to the Saba
menus, instead of providing a portlet that users can add to their portal pages. For more information about adding new
application pages into the menu system, refer to the System Administrator Guide.

The Control File


The control file for this application page is much like the control file for the portlets described earlier in the tutorial.

<?xml version="1.0" encoding="UTF-8"?>

<?cocoon-process type="wdk"?> <!-- A standalone page, not a portlet-->

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:wdk="http://www.saba.com/XML/WDK">

<!-- Saba reserves IDs up to 500000 -->

<rdf:Description id="522222">

<rdf:type resource="http://www.saba.com/XML/WDK/Control"/>

<wdk:version>1.0</wdk:version> <wdk:model
rdf:resource="tutorialCommandPage.xml"/> <wdk:view
rdf:resource="tutorialCommandPage.xsl"/> <wdk:widgets
rdf:resource="/wdk/xsl/widget/wdk_widgets.xsl"/>

</rdf:Description>

</rdf:RDF>
In this case, we assume that the control file is in the same directory as the model and view files, and that all three files
have the root name tutorialCommandPage (with the various appropriate file extensions). We give this application
page the ID number 522222, and designate it as a standalone application page, instead of a portlet.

The Command Object


The core of this new application page is its command object. A model file can run Java code by including a
<wdktags:execute> directive, which is compiled into an instruction to create a particular object and run a particular
method in that object. The <wdktags:execute> directive offers several different calling methods. This example

| WDK Page Tutorial | 332

uses the simplest version of that directive, in which it specifies that a particular command object should be created, and
that command object's execute method should be invoked.
A command object must implement Saba's ICommand interface.Usually, it does this by extending the
AbstractCommand class. If you create a new class based on AbstractCommand, you must provide two methods:

You must write a no-argument constructor for the object. If the command object expects to be passed parameters,
the constructor method performs some initial processing. If the command object doesn't pass or return parameters,
you can write an empty constructor (as in this example).
You must write the doExecute method. This method is what generates the XML output. The method does this by
using a visitor. The visitor is a special-purpose object created by AbstractCommand's code, which produces XML
representations of Java objects. If the visitor examines a simple Java type (like a number or a string), it generates an
appropriate XML node. If it visits Saba object which implements IXMLObject, it generates a node for the object,
then recursively visits each field in the object. Thus, if you want to generate an XML representation of some Saba
data, all you usually need to do is create the appropriate Saba object, then pass it to the XML visitor.

| WDK Page Tutorial | 333

In the case of this example, the command object builds its XML output a node at a time, creating a static data set.

/*

* TutorialCommand.java

*/

package com.mycorp.tutorial

import java.util.*;

import javax.servlet.http.HttpServletRequest;

import com.saba.web.dk.AbstractCommand;

import com.saba.xml.IXMLVisitor;

import com.saba.xml.SabaXMLType;

/**

* A test command object. It is not passed any parameters. It

* generates the XML output manually.

*/

public class TutorialCommand extends AbstractCommand

| WDK Page Tutorial | 334

/** Default constructor; doesn't need any code

*/

public TutorialCommand()

/* This command object doesn't need any initialization. */

/**

* This command generates the output. In this case, it does so

* by using the IXMLVisitor to manually build the output, node

* by node.

* This example generates output with the format

<people>

<person>

<last_name>Smith</last_name>

<first_name>John</first_name>

</person>

| WDK Page Tutorial | 335

<!-- ...other 'person' nodes...-->

</people>

*/

public void doExecute( HttpServletRequest request,

IXMLVisitor visitor )

throws Exception

// Build the XML output node by node.

visitor.beginVisit(null, "people", null, null, null);

visitor.beginVisit(null, "person", null, null, null);

visitor.visit(null, "last_name", "Malfoy");

visitor.visit(null, "first_name", "Draco");

visitor.endVisit(null, "person");

visitor.beginVisit(null, "person", null, null, null);

visitor.visit(null, "last_name", "Dumbledore");

visitor.visit(null, "first_name", "Albus");

| WDK Page Tutorial | 336

visitor.endVisit(null, "person");

visitor.beginVisit(null, "person", null, null, null);

visitor.visit(null, "last_name", "Weasley");

visitor.visit(null, "first_name", "George");

visitor.endVisit(null, "person");

visitor.endVisit(null, "people");

}
This object must be compiled, and the class file placed in your application server's class path.

Model File with <wdktags:execute>


The model file for this example is fairly simple. It is much like the model files used by the portlets earlier in the tutorial.
It has two significant differences:

Instead of containing the data directly in the file, the model file invokes a command object to generate XML data.
(It uses the command object defined in the last section, TutorialCommand.)
The model file uses a table widget to display the XML data. The model file attaches the table to the data generated
by the command object, by using a <wdktags:attachTo> directive. The table widget automatically displays as
many rows as are needed by the data.

<?xml version="1.0"?>

<xsp:page language="java"

| WDK Page Tutorial | 337

xmlns:xsp="http://www.apache.org/1999/XSP/Core"
xmlns:wdktags="http://www.saba.com/XML/WDK/taglib">

<xsp:structure>

<xsp:include>com.saba.*</xsp:include>

</xsp:structure>

<wdk:page xmlns:wdk="http://www.saba.com/XML/WDK">

<!-- the head element contains standard header data


for all WDK model files -->

<wdk:head>

<!-- No input or output parameters for this portlet. -->

<!-- the title of the page or portlet -->

<wdk:title>Test of Command Object</wdk:title>

<!-- Load the resource bundle... -->

<wdktags:i18n.load resource="my_custom_pages"/>

<!-- WDK labels, usable by the view file -->

<wdk:labels /> <!-- No labels needed -->

</wdk:head>

<wdk:form method="POST">

<!-- Model information is in this tag. This is where we make the call to

| WDK Page Tutorial | 338

the command object. -->

<wdk:model>
<!-- In the output, the following tag is replaced
by
the XML node output from the command object.
-->
<wdktags:execute
commandObj="com.mycorp.tutorial.TutorialCommand"

/>

</wdk:model>

</wdk:form>

<!-- Widgets are defined here. -->

<wdk:widgets>
<wdk:table name="peopleTable">
<!-- The attachTo node
indicates that this widget is
linked to the 'people' node created by the
command object. -->
<wdktags:attachTo path="people"/>
<head> <!-Defines the heading row -->
<!-- Usually, you would use internationalizable labels for the heading row's labels.
In this example, we use
static text for
simplicity. -->
<column>First Name</column>
<column>Last Name</column>
</head>
<row path="person"> <!-- i.e. one
row for each
'person' node -->
<column>
<wdktags:nodeRef path="first_name"/>
</column>
<column>
<wdktags:nodeRef path="last_name"/>
</column>
</row>
</wdk:table>
</wdk:widgets>

</wdk:page>

</xsp:page>

| WDK Page Tutorial | 339

The <wdktags:execute> directive invokes the command object; as we saw, that object generates the static XML
output

<people>

<person>

<last_name>Malfoy</last_name>

<first_name>Draco</first_name>

</person>

<person>

<last_name>Dumbledore</last_name>

<first_name>Albus</first_name>

</person>

<person>

<last_name>Weasley</last_name>

<first_name>George</first_name>

</person>

<people>
The model file also specifies that a table widget should be created, and attached to the <people> node. The table
widget specifies that it should have one row for each <person> node under the <people> node. Each row, in turn,
has one column for the first name and one for the last name. The widget thus generates a table like this:
First Name

Last Name

Draco

Malfoy

| WDK Page Tutorial | 340

First Name

Last Name

Albus

Dumbledore

George

Weasley

Note that the table displays first name before last name, which is different from the order in the command object's output.
When you define the table widget, you are perfectly free to specify what order you want the data to appear in. You can
also choose to omit some of the data returned by your command object. For example, you could easily create a table
which displayed last names.

View File
This view file is very straightforward. All the model file's data is presented in a table widget. The view file needs to
display that widget.

<?xml version="1.0" encoding="UTF-8"?>

<?wdklint ignoreRules="XSL_HTML"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"


xmlns:wdk="http://www.saba.com/XML/WDK"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rss10="http://purl.org/rss/1.0/">

<xsl:import
href="/wdk/xsl/view/wdk_defaultview.xsl"/>

<xsl:import
href="/wdk/xsl/view/wdk_widgetlayout.xsl"/>

<xsl:template match="wdk:model">
<xsl:apply-templates
select="$wdkWidget[@name='peopleTable']"/> </xsl:template>

</xsl:stylesheet>

Chapter

12
Tips and Tricks
Topics:

Overview
Making SQL Calls from a Client
Command
Using Dynamic Delegates
Extending Saba's EJBs
Extending the Saba EJB
Modifying ejb-jar.xml

This appendix describes several useful techniques for working with the Saba
Cloud platform.

| Tips and Tricks | 342

Overview
This manual covers the basic techniques for writing application pages and Java code to interact with the Saba Cloud
platform. However, some coding tricks and techniques do not fall into any category and are useful in only a few situations.
This appendix describes several such techniques. This appendix is not intended to be a comprehensive guide to Saba
Cloud programming. Instead, it is a cookbook of techniques that our own engineers and consultants find useful.

Making SQL Calls from a Client Command


The ordinary way to retrieve information from the Saba data store is to make calls to the appropriate Java object. These
objects, in turn, use the system's engine to interact with the underlying SQL database. However, in some circumstances
you may find it useful to make a call directly to the database from your Java code. This example illustrates how you
might do that.
Important: You should not make changes to the database through direct SQL commands. In addition, the only
supported interface to the SQL database is through Saba's public views, not by examining the tables directly.
Note that the following technique is only appropriate for reading data that is fairly static, that is, that is not likely to
change during the course of your operation. To examine data that might be changing, look at the delegate objects, as
described in "Using Dynamic Delegates".

| Tips and Tricks | 343

The following code might be placed in a client command object to access the database. In this example, the code queries
the cmv_pub_smp_person view to find the first and last name of a particular person.
private String[] getName(Person person) throws SabaException {
String sql = //replace below with whatever SQL query you want
"select fname, lname from cmv_pub_smp_person where id=?";
String[] name=new String[2];
ConnectionManager cm=mLocator.getConnectionManager();
Connection conn=cm.getConnection(mLocator.getSabaPrincipal().getSiteName());
PreparedStatement stat=null;
ResultSet rs=null;
try {
stat=conn.prepareStatement(sql);
stat.setString(1,person.getId());
rs=stat.executeQuery();
if(rs.next()){
name[0]=rs.getString(1);
name[1]=rs.getString(2);
//System.out.println("value of name:"+name[0]+"
}
}
catch(SQLException x){
Debug.GetStackTrace(x);
x.printStackTrace();
throw new SabaException (x);

"+name[1]);

| Tips and Tricks | 344

}
finally {
try {
rs.close();
stat.close();

cm.freeConnection(conn);
}
catch(SQLException x){
// do nothing
}

return name;
}

Using Dynamic Delegates


You can use Saba's dynamic delegates to write transactional code without writing and deploying an entire EJB. The
transactional code runs within a Saba EJB.
To use dynamic delegates, follow these steps:
1. Define a Java interface, and a class which implements that interface, to contain your methods. The interface must
implement the com.saba.locator.delegate.IDelegateDynamic interface, and the class must extend
the com.saba.ejb.DynamicSessionBean class. Your code can use the
ServiceLocator.getConnectionManager() method to access the EJB server's connection pool:

getServiceLocator().getConnectionManager().getConnection(
getSiteName());
For information about the ServiceLocator, DynamicSessionBean, and IDelegateDynamic classes,
consult the API reference documentation.

| Tips and Tricks | 345

2. Put the compiled code in customizations.jar, and store that archive in saba.ear.
3. Obtain an instance of your object by calling the ServiceLocator.getDynamicDelegate() method, passing
it the name of your interface and class. You might do this from inside a command or model object, or from Java
code included in a model file.
All the code in that class is executed on a transactional basis. You can be useful, for example, when you run transactional
database code.

Extending Saba's EJBs


In some circumstances, you may need to extend Saba EJBs to add functionality. This is useful if you want to link one
one action to another. For example, in this section we show how to extend Saba's performance plans to automatically
launch a performance review for a user whenever he or she finishes a performance plan.
To extend an EJB, follow these steps:
1. Find the appropriate Saba EJB. These are not formally documented. However, you should be able to find the objects
by examining the API reference guide. The workflow objects usually implement a workflow interface which is
documented. For example, in this case we extend the ActionPlanWorkflowEJB, which implements the
ActionPlanWorkflow interface.
2. Create a new class that extends the existing EJB. Write methods where you want to extend the functionality. However,
make sure that each of your modified methods calls its superclass's method. For example, in this case we modify the
moveActivatedActionPlanToCompleted method, having it first call its superclass method, then calling a
private AssignPerformanceReview method to add the review.
3. Modify the EJB deployment descriptor file ejb-jar.xml (in saba.ear). Find the reference to the extended
EJB, and change it to a reference to your new class.
4. Compile your new class and install it normally, and restart the application server.

Extending the Saba EJB


In this example, we extend the Saba EJB ActionPlanWorkflowEJB, adding functionality to the
moveActivatedActionPlanToCompleted method. The key part of this example is the extended method, which

| Tips and Tricks | 346

invokes its superclass method, then calls a new, private assignPerformanceReview method to provide the new
functionality.
/* Class which extends Saba's ActionPlanWorkflowEJB */

package com.sab

a.custom ;

import com.saba.actionplan.*;
import com.saba.actionplancycle.business.*;
import com.saba.exception.SabaException;
import com.saba.locato r. ServiceLocator;
import com.saba.customattribute.*;
import com.saba.review.*;

import java.util.*;

/**
* Customizes the standard ActionPlanWorkflowEJB to automatically create
* a review once the action plan is completed.
*/public class CustomActionPlanWorkflowEJB extends ActionPlanWorkflowEJB
{

public void moveActivatedActionPlanToCompleted(


final ActionPlan actionPlan,
final ActionPlanDetail actionPlanDetail ) throws SabaException
{
// call superclass

| Tips and Tricks | 347

super.moveActivatedActionPlanToCompleted(actionPlan,
actionPlanDetail);
// attach performance review when the action plan is completed
assignPerformanceReview(actionPlan, actionPlanDetail);
}
/**
* attach performance review when the action plan is completed
*
*/
private void assignPerformanceReview( final ActionPlan actionPlan, final
ActionPlanDetail actionPlanDetail ) throws SabaException
{
// get the action form for the action plan
ActionForm actionForm = actionPlanDetail.getActionForm();
ActionFormManager actionFormManager
= (ActionFormManager)
getServiceLocator().getManager(Delegates.kActionFormManager);
ActionFormDetail actionFormDetail = actionFormManager.getDetail(actionForm);

| Tips and Tricks | 348

// check if the action plan has the review attached using a value stored in
a custom field
ICustomAttributeValueDetail custom1Value =
(ICustomAttributeValueDetail)actionFormDetail.getCustomValue("custom1");
if(custom1Value != null)
{
// get the review reference
String id = custom1Value.toString();
ReviewForm reviewForm = (ReviewForm)
ServiceLocator.getReference(ReviewForm.class, id);

// create the review


ReviewManager reviewManager =
(ReviewManager)getServiceLocator().getManager(Delegates.kReviewManager);
Review review = reviewManager.createReview(reviewForm,
(Party)actionPlanDetail.getAssignee());

| Tips and Tricks | 349

// set the status, start date and end date of the review
ReviewDetail reviewDetail = reviewManager.getReviewDetail(review);
reviewDetail.setStartDate(actionPlanDetail.getStartDate());
reviewDetail.setEndDate(actionPlanDetail.getEndDate());
reviewDetail.setReviewState(ReviewState.kActive);
reviewManager.setReviewDetail(review, reviewDetail);
}

} /* assignPerformanceReview */

} /* CustomActionPlanWorkflowEJB */

Modifying ejb-jar.xml
Once you have created your new subclass, you must modify Saba's deployment descriptor file to call your new class.
find the reference to the existing EJB and change it.

| Tips and Tricks | 350

For example, in this case we have written a new class named CustomActionPlanWorkflowEJB which extends
Saba's ActionPlanWorkflowEJB. The relevant portion of the ejb-jar.xml file is:

<session id="ActionPlanWorkflow">
<ejb-name>ActionPlanWorkflow</ejb-name>
<home>com.saba.actionplancycle.business.ActionPlanWorkflowBeanHome</home>
<remote>com.saba.ejb.IDynamicStatelessBean</remote>

<ejb?class>com.saba.actionplancycle.business.ActionPlanWorkflowEJB</ejb?class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type> <security-role-ref>
<role-name>sabauser</role-name>
<role-link>sabauser</role-link>
</security-role-ref>
</session>
We make the following change (marked in bold):

<session id="ActionPlanWorkflow">
<ejb-name>ActionPlanWorkflow</ejb-name>
<home>com.saba.actionplancycle.business.ActionPlanWorkflowBeanHome</home>
<remote>com.saba.ejb.IDynamicStatelessBean</remote>

| Tips and Tricks | 351

<ejb?class>com.saba.custom.CustomActionPlanWorkflowEJB</ejb?class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
<security-role-ref>
<role-name>sabauser</role-name>
<role-link>sabauser</role-link>
</security-role-ref>
</session>

Chapter

13
Sample Java Program
Topics:

Sample Code Overview


Sample Code Files
run.bat
APIExamples.java

The Saba application provides a sample of Java code which uses the Saba API.
The code is provided on the product CD in the
toolkit\developer\examples\java directory. In addition, the files
are listed here, with descriptions.

| Sample Java Program | 354

Sample Code Overview


This code runs through several typical operations:

Starting a Saba session and creating a ServiceManager


Searching for particular items in the data store
Modifying, adding, and deleting data

Besides demonstrating how various API classes work, the sample code can be useful as a skeleton for your own test
code. By modifying the main() method, you can have the program run other test methods which you might add to the
class.
The code must be compiled and run on a machine with a working Saba installation.

Files
The API sample contains the following files:

APIExamples.java This contains all the Java code. You need to modify some of the file's constants before
compiling.
build.bat A script for compiling the sample code. You need to edit this file to put in the full paths of the
saba.jar, sabares.jar, and ejb2.0.jar files.

Configuring the Sample Code


Before you run the sample code, you need to edit some of the source files to give them appropriate information for your
Saba installation.

APIExamples.java
Edit the constants kUsername and kPassword, giving them appropriate values for your Saba site. You should not
need to edit any of the other constants.

build.bat
Edit the file's set commands to contain the paths to saba.jar, sabares.jar, and ejb2.0.jar on your installation.

run.bat
Make sure the file's JBOSS_DIST variable is set to the installation's JBoss directory.

Compiling and Running


To compile the sample code, run build.bat from the directory containing the code. This generates the file
APIExamples.class.
To run the sample code, follow these steps:
1. Make sure Saba is not currently running on the machine.

| Sample Java Program | 355

2. Run the script run.bat to launch Saba and run the sample code. The script launches Saba, then launch
APIExamples.

Sample Code Files


This section contains the text of the various sample code files. In all cases, the most recent versions are on the
documentation CD. They are also provided here for reference.
The printed versions in this section may contain line breaks which do not appear in the source code (in order to let the
code fit on the page).

| Sample Java Program | 356

build.bat
@echo off

REM set the location of saba jar

set _saba_jar=D:\buildJar\lib\saba.jar

REM set the location of the sabares jar

set _sabares_jar=D:\cvswork\sabahcdm\libraries\sabares.jar

REM set the location of the ejb jar

SET _ejb_jar=D:\cvswork\sabahcdm\libraries\java\lib\ejb2.0.jar

echo Building API examples

javac -classpath %_saba_jar%;%_sabares_jar%;%_ejb_jar% -deprecation


APIExamples.java

run.bat
@echo off

REM set the location of saba jar

set _saba_jar=D:\buildJar\lib\saba.jar

REM set the location of the sabares jar

set _sabares_jar=D:\cvswork\sabahcdm\libraries\sabares.jar

| Sample Java Program | 357

REM set the location of the ejb jar

SET _ejb_jar=D:\cvswork\sabahcdm\libraries\java\lib\ejb2.0.jar

echo Building API examples


javac -classpath %_saba_jar%;%_sabares_jar%;%_ejb_jar% -deprecation APIExamples.java

| Sample Java Program | 358

APIExamples.java
/*

===================================================================

* (c) Copyright 1997-2004, Saba Software

All Rights Reserved

Company Confidential

===================================================================

*/

import com.saba.locator.*;

import com.saba.exception.*;

import com.saba.security.SabaLogin;

import com.saba.party.*;

| Sample Java Program | 359

import com.saba.party.person.*;

import com.saba.party.organization.*;

import com.saba.i18n.*;

import com.saba.location.*;

import com.saba.domain.*;

import com.saba.function.*;

import com.saba.primitives.*;

import com.saba.xml.*;

import com.saba.security.*;

import com.saba.catalog.*;

import com.saba.learningoffering.delivery.*;

import com.saba.nlevel.*;

import com.saba.learning.registration.*;

import com.saba.order.*;

import com.saba.currency.*;

import com.saba.learning.order.*;

import com.saba.learningoffering.*;

| Sample Java Program | 360

import com.saba.offering.*;

import com.saba.learning.resource.facility.*;

import com.saba.calendar.timeperiod.*;

import com.saba.courselanguage.*;

import com.saba.learningoffering.sessiontemplate.*;

import com.saba.attachment.*;

import com.saba.finder.FinderIterator;

import java.io.*;

import java.util.*;

import java.math.BigDecimal;

import java.sql.Timestamp;

import java.text.DateFormat;

/**

* Test program to demonstrate the use of various Saba APIs.

* <b>IMPORTANT:</b> This example documents the APIs as found in Saba

| Sample Java Program | 361

* 5.1. Some of these APIs are not yet public and are subject to change in
release 5.2 to

* improve their usability.

In particular, you can expect the

* datatypes to be friendlier and the detail constructors to be much

* simpler.

* @author Daniel Lipkin

*/

public class APIExamples

/* database-specific constants for various operations performed by

the examples - these need to be modified to reflect the database

the tests are run against */

private static final String kUsername = "uone";

private static final String kPassword = "welcome";

private static final String kBusinessUnitName = "Marketing";

private static final String kManagerUserName = "UONE";

| Sample Java Program | 362

private static final String kLocationName="New York";

private static final String kTitle = "Communication Skills for Career Growth";

private static final String kSecurityListName = "Guest";

private static final String kILTName = "Instructor-Led";

private static final String kILTOfferingName = "Communication";

private static final String kWBTOfferingName = "Defining Projects";

private static final DateFormat mDateFormat

= DateFormat.getDateInstance();

public static void main(String[] args) {

/* login and perform each action in turn */

try {

/* login and obtain an authenticated ServiceLocator.

All

other methods uses this locator to obtain manager

instances and other business objects. */

ServiceLocator locator = SabaLogin.authenticate(kUsername,

kPassword);

APIExamples theExamples = new APIExample

| Sample Java Program | 363

s();

Employee employee = theExamples.createUser(locator);

theExamples.browseCatalog(locator);

theExamples.orderClass(locator);

theExamples.viewProfile(locator);

theExamples.updateUser(locator, employee);

theExamples.createClass(locator);

catch (Exception e)

e.printStackTrace();

System.exit(0);

} /* main */

/**

* Demonstrates use of PartyManager and SabaSecurityManager

* interfaces to create a new user and assign privileges to

| Sample Java Program | 364

* the user.

* @param locator ServiceLocator instance

* @return New user's <code>Employee</code> object.

*/

public Employee createUser(ServiceLocator locator) throws Exception

System.out.println("start of createUser");

/* Obtain an instance of PartyManager - this is the remote

delegate used for all operations on clients, employees,

companies, and business units. */

PartyManager partyManager = (PartyManager)

locator.getManager(Delegates.kPartyManager);

/* To create a new employee, we first construct an employee

detail - a value object containing all dependent information

for an employee. */

/* Search for an existing business unit by name. */

| Sample Java Program | 365

Collection businessUnits =

partyManager.findBusinessUnitByName(kBusinessUnitName);

Iterator i = businessUnits.iterator();

if (!i.hasNext()) {

throw new Exception("Unexpected error in createUser - "

+ "no business unit found");

BusinessUnit businessUnit = (BusinessUnit) i.next();

/* Search for an existing person by user name. findEmployee()

is a useful method that allows matching against several search

criteria.

In this case, since we want to match on user name,

we pass in that value and leave all other fields null. */

Collection employees

= partyManager.findEmployee("", // first name

"", // last name

kManagerUserName, // user name

"", // organization

| Sample Java Program | 366

"", // location

""); // employee number

i = employees.iterator();

if (!i.hasNext()) {

throw new Exception("Unexpected error in createUser - "

+ "no manager found");

/* Results returned by findEmployee() is a List of String

values containing detail information.

The first two columns

are the employee's id and display name. */

List managerInfo = (List) i.next();

String id = (String) managerInfo.get(0);

String displayName = (String) managerInfo.get(1);

Employee manager = (Employee)


ServiceLocator.getReference(Employee.class, id, displayName);

Location location = getLocation(kLocationName, locator);

| Sample Java Program | 367

if (location == null)

throw new Exception("Unexpected error in createUser - "

+ "no location found");

/* For locale, time zone, and domain, we are using the

pre-defined root values.

Because these are well-known values,

we can use ServiceLocator to directly obtain an opaque

reference to these objects.

This avoids the need to incur a

database hit by calling a manager's find() method. */

SabaLocale locale =

(SabaLocale) ServiceLocator.getReference(SabaLocale.class,

"local000000000000001",

"English");

SabaTimeZone timezone =

(SabaTimeZone) ServiceLocator.getReference(SabaTimeZone.class,

"tzone000000000000005",

"(GMT-08:00) Pacific Time (US & Canada), Tijuana");

| Sample Java Program | 368

Domain domain =

(Domain) ServiceLocator.getReference(Domain.class,

"domin000000000000001",

"world");

JobType jobType =

(JobType) ServiceLocator.getReference(JobType.class,

"jobtp000000000001001",

"Architect");

/* Define other constants required by the Detail constructor. */

String employeeNumber="10075";

Calendar calculateDate = Calendar.getInstance();

calculateDate.set(2002, 1, 1);

String ssn = "111-11-1111";

String jobTitle = "Captain";

/* Define other primitives required by the Detail constructor. */

NameDetail name = new NameDetail("Captain", // title

"Bialar", // first name

| Sample Java Program | 369

"Crais", // last name

""); // middle name

AddressDetail address =

new AddressDetail("2400 Bridge Parkway", // addr1

"", // addr2

"Redwood Shores", // city

"CA", // state

"94065", // zip

"U.S.A."); // country

ContactInfoDetail contact =

new ContactInfoDetail("650-888-8888", // phone1

"", // phone2

"", // fax

"bcrais@peacekeper.com"); // email

SecurityInfoDetail security =

new SecurityInfoDetail("bcrais", // username

"talyn"); // password

| Sample Java Program | 370

/* construct a new EmployeeDetail.


minimal constructor, then setting

Note that we are first using the

* additional values directly on the detail. */

EmployeeDetail detail = new EmployeeDetail(

name,

security,

employeeNumber,

timezone,

businessUnit,

locale,

domain,

Gender.MALE

);

detail.setManager(manager);

detail.setLocation(location);

detail.setJobtype(jobType);

detail.setSsNo(ssn);

| Sample Java Program | 371

detail.setJobTitle(jobTitle);

detail.setAddressDetail(address);

detail.setContactInfoDetail(contact);

/* Now we can finally create the employee. */

Employee employee =

(Employee) partyManager.createEmployee(detail);

/* Now use the security API to assign this employee to a

security list. */

SabaSecurityManager securityManager = (SabaSecurityManager)

locator.getManager(Delegates.kSabaSecurityManager);

/* Find the security list containing standard user privileges */

Collection securityLists =

securityManager.findSecurityListByName(kSecurityListName);

i = securityLists.iterator();

if (!i.hasNext()) {

throw new Exception("Unexpected error in createUser - "

+ "no security list found with name " + kSecurityListName);

| Sample Java Program | 372

ISecurityList securityList = (ISecurityList) i.next();

/* Add the new employee to the security list.

Fortunately, the

Emnployee interface directly extends ISecurityListMember, so we

can pass in the reference we already have. */

securityManager.addMember(securityList, employee);

System.out.println("end of createUser");

return employee;

} /* createUser */

/**

* Returns a Location reference given a location name.

* @param locationName

* @param locator ServiceLocator

* @return Location, or null if none found

*/

private Location getLocation(String locationName,

ServiceLocator locator)

| Sample Java Program | 373

throws SabaException

/* Search for an existing location by name.

Uses a remote

delegate - because this is an entity-style API, we use the

delegate's home interface to locate the object. */

LocationHome locationHome =

(LocationHome) locator.getHome(Delegates.kLocation);

Collection locations = locationHome.findByName(locationName);

Iterator i = locations.iterator();

if (!i.hasNext()) {

return null;

Location location = (Location) i.next();

return location;

} /* getLocation */

/**

| Sample Java Program | 374

* Demonstrates use of CatalogManager

* interface to search and browse the learning catalog.

* @param locator ServiceLocator instance

*/

public void browseCatalog(ServiceLocator locator) throws Exception


{

System.out.println("start of browseCatalog");

/* Obtain an instance of CatalogManager - used to search the

learning catalog.

*/

CatalogManager catalogManager = (CatalogManager)

locator.getManager(Delegates.kCatalogManager);

/* Search #1: ILT classes in a specified location with a

specified keyword, available after today. */

/* Obtain a Delivery reference for ILT */

DeliveryHome deliveryHome =

(DeliveryHome) locator.getHome(Delegates.kDelivery);

| Sample Java Program | 375

Collection deliveries = deliveryHome.findDeliveryByName(kILTName);

Iterator i = deliveries.iterator();

if (!i.hasNext()) {

throw new Exception("Unexpected error in browseCatalog - "

+ "no ILT Delivery found");

/* turn the Delivery reference into a Delivery id String */

Delivery delivery = (Delivery) i.next();

Collection deliveryIds = new ArrayList();

deliveryIds.add(delivery.getId());

/* get the location */

Location location = getLocation(kLocationName, locator);

/* Construct a search detail and set the appropriate

parameters. The system will calculate appropriate defaults for

conditions like base customer and territory based on the

| Sample Java Program | 376

current user. */

OfferingDriverData condition = new OfferingDriverData();

condition.setTitle(kTitle);

condition.setLocation(location);

condition.setDeliveries(deliveryIds);

condition.setIsLearner(true);

/* At last we are ready to perform the search. */

FinderIterator results =
catalogManager.findOfferings(condition).getFinderIterator();

/* Obtain the total result count. */

int count = results.size();

System.out.println("Search #1 has " + count + " results.");

/* Obtain all results.

*/

while (results.hasNext()) {

OfferingResult oneRow = (OfferingResult) results.next();

printOffering(oneRow);

System.out.println();

| Sample Java Program | 377

/* browse for learning */

NLevelHierarchyManager hierarchyManager = (NLevelHierarchyManager)

locator.getManager(Delegates.kNLevelHierarchyManager);

Collection folders = hierarchyManager.findTopLevelFolders();

/* print out each folder */

i = folders.iterator();

Folder folder = null;

System.out.println("Top level categories:");

while (i.hasNext()) {

folder = (Folder) i.next();

System.out.println(folder.getDisplayName());

/* search for offerings contained in the first folder */

folder = (Folder) folders.iterator().next();

condition = new OfferingDriverData();

condition.setCategory(folder, true);

| Sample Java Program | 378

condition.setIsLearner(true);

/* obtain a new CatalogManager to handle this search */

catalogManager = (CatalogManager)
locator.getManager(Delegates.kCatalogManager);

results = catalogManager.findOfferings(condition).getFinderIterator();

count = results.size();

System.out.println("Search #2 has " + count + " results.");

/* Obtain all results.

*/

while (results.hasNext()) {

OfferingResult oneRow = (OfferingResult) results.next();

printOffering(oneRow);

System.out.println();

System.out.println("end of browseCatalog");
} /* browseCatalog */

/**

Helper method to print out the results from the data returned by the

| Sample Java Program | 379

catalog search.

*/

private void printOffering(OfferingResult row) throws SabaException

String classID = row.getPrimaryKey().toString();


String courseName = row.getOffTemp().getDisplayName();

String deliveryName = row.getDelivery().getDisplayName();

String locationName = new String();

Location location = row.getLocation();

if (location != null)

locationName = location.getDisplayName();

String formattedDate = new String();

Date startDate = row.getStartDate();

if (startDate != null)

formattedDate = mDateFormat.format(startDate);

| Sample Java Program | 380

BigDecimal cost = row.getAmount();

System.out.println("Class " + classID + ": " + courseName

+ ". Type=" + deliveryName

+ "; location=" + locationName

+ "; start date=" + formattedDate

+ "; cost=$" + cost.toString());


} /* printOffering */

/**
* Helper method to search for a specific offering by title.

* This method will return the first match found.

* @param title String title of offering

* @param locator ServiceLocator

* @return String id uniquely identifying the offering, or null if no


offerings found

*/

private String findOffering(String title, ServiceLocator locator)

throws Exception

| Sample Java Program | 381

/* obtain an instance of the catalog manager */

CatalogManager catalogManager = (CatalogManager)

locator.getManager(Delegates.kCatalogManager);

OfferingDriverData condition = new OfferingDriverData(title, locator);

/* do the search and return the first row */

Iterator i = catalogManager.findOfferings(condition).iterator();

if (!i.hasNext()) return null;

OfferingResult offeringData = (OfferingResult) i.next();

String offeringID;

if (offeringData != null) {

offeringID = offeringData.getPrimaryKey().toString();

else

offeringID = null;

| Sample Java Program | 382

return offeringID;

} /* findOffering */

/**

* Demonstrates use of LearningOrderManager and LearningOrderServices

* interface to place an order for a learning event.

* @param locator ServiceLocator instance

*/

public void orderClass(ServiceLocator locator) throws Exception

System.out.println("start of orderClass");

/* LearningOrderManager is a stateful delegate.

We use the

home interface to obtain an instance of the manager.

We then

create an order, add order items to it, and save the order.

Finally we must free our instance of the

LearningOrderManager. */

LearningOrderManagerHome learningOrderHome =

| Sample Java Program | 383

(LearningOrderManagerHome)

locator.getHome(Delegates.kLearningOrderManager);

/* place this order to the current user's business unit */

UserManager userManager = (UserManager)

locator.getManager(Delegates.kUserManager);

IUser user = userManager.getCurrentUser();

/* Use ServiceLocator to morph the IUser into an instance of

* Party. */

Employee employee = (Employee)

ServiceLocator.getReference(Employee.class, user.getId());

BusinessUnit billToParty = (BusinessUnit)

(employee.getEntity(locator).getDetail().getCompany());

/* use well-known values for Currency and Domain */

SabaCurrency currency = (SabaCurrency)

ServiceLocator.getReference(SabaCurrency.class,

"crncy000000000000001",

"US Dollars");

| Sample Java Program | 384

Domain domain = (Domain)

ServiceLocator.getReference(Domain.class,

"domin000000000000001",

"world");

LearningOrderDetail loDetail =

new LearningOrderDetail(null, // client

billToParty, // billTo

employee, // contact

currency, // currency
domain, // domain

employee, // baseCustomer

null); // soldBy

/* create the order.

This returns an instance of a stateful

* delegate to manage the order. */

System.out.println("creating order");

LearningOrderManager manager =
learningOrderHome.create(loDetail);

/* add an ILT item to the order. */

| Sample Java Program | 385

String offeringID = findOffering(kILTOfferingName, locator);

ILTOffering offering = (ILTOffering)


ServiceLocator.getReference(offeringID);

BigDecimal price = new BigDecimal(50.00);

OrderItemDetail detail = (OrderItemDetail)

"Leviathan Care and Feeding", // description

price, // price

offering, // part

"Very limited attendance", // notes

(Person) employee); // student

try {

System.out.println("adding ILT");

manager.addToOrder(detail);

catch (SabaException e)

| Sample Java Program | 386

/* There may be business logic that prevents registration

for a specific ILT - ie schedule conflicts or unsatisfied

prerequisites.

Catch any exceptions so that the rest of

the order can proceed. */

System.out.println("Unable to add ILT:

" + e.getMessage());

/* add a WBT item to the order. */

offeringID = findOffering(kWBTOfferingName, locator);

WBTOffering wbtOffering = (WBTOffering)


ServiceLocator.getReference(offeringID);

price = new BigDecimal(75.00);

detail = (OrderItemDetail)

new DownloadableDetail("DRD Repair and Maintenance", // description

price, // price

1, // required quantity

wbtOffering, // part

"Our most popular WBT", // notes

| Sample Java Program | 387

(Person) employee); // student

try {

System.out.println("adding WBT");

manager.addToOrder(detail);

catch (SabaException e)

System.out.println("Unable to add WBT:

" + e.getMessage());

/* now we can save the order */

System.out.println("saving order");

Order order = manager.saveOrder();

/* now free up the manager */

manager.remove();

/* Print out details about the order.


so use the

This is a stateless operation,

LearningOrderServices interface for this. */

| Sample Java Program | 388

LearningOrderServices learningOrderServices =

(LearningOrderServices)

locator.getManager(Delegates.kLearningOrderServices);

LearningOrderDetail orderDetail =

learningOrderServices.getDetail(order);

String receiptNo = orderDetail.getOrderNo();

BigDecimal totalCharges = orderDetail.getTotalCharges();

OrderStatus status = orderDetail.getStatus();

String statusName = status.getName();

System.out.println("Order #" + receiptNo

+ " entered with status " + statusName

+ "; total cost = " + totalCharges.toString());

Vector orderItems = learningOrderServices.getAllOrderItems(order);

Iterator i = orderItems.iterator();

while (i.hasNext()) {

/* print out some detail information about each item in the order
*/

OrderItem orderItem = (OrderItem) i.next();

| Sample Java Program | 389

OrderItemDetail orderItemDetail =

learningOrderServices.getDetail(orderItem);

System.out.println("order item #"

+ orderItemDetail.getItemNo()

+ " part #"

+ orderItemDetail.getPart().getDisplayName()

+ ": " + orderItemDetail.getDescription());

System.out.println("end of orderClass");

} /* orderClass */

/**

* Demonstrates use of PartyManager and RegistrationManager

* interfaces to retrieve learner profile and history.

* @param locator ServiceLocator instance

*/

public void viewProfile(ServiceLocator locator) throws Exception

| Sample Java Program | 390

System.out.println("start of viewProfile");

/* Search for the employee we created in the createUser step. */

PartyManager partyManager = (PartyManager)

locator.getManager(Delegates.kPartyManager);

Collection employees =

partyManager.findEmployee("", // first name

"Crais", // last name

"", // user name

"", // organization

"", // location

""); // employee number

Iterator i = employees.iterator();

if (!i.hasNext()) {

throw new Exception("Unexpected error in viewProfile - "

+ "no employee found");

| Sample Java Program | 391

List empInfo = (List) i.next();

String id = (String) empInfo.get(0);

Employee emp = (Employee) ServiceLocator.getReference(Employee.class,


id);

/* Now obtain his detail information and serialize it as XML. */

EmployeeDetail detail = partyManager.getDetail(emp);

PrintWriter writer = new PrintWriter(System.out);

IXMLVisitor visitor = XML.getDefaultXMLVisitor(writer);

visitor.visit(null, detail.getTagName(), detail, null);

writer.flush();

/* now print the learner's learning history */

RegistrationManager registrationManager = (RegistrationManager)

locator.getManager(Delegates.kRegistrationManager);

Collection registrations =

registrationManager.getRegistrationsForLearnerH(emp);

i = registrations.iterator();

| Sample Java Program | 392

int count = 0;

while (i.hasNext()) {

Registration r = (Registration) i.next();

/* print out details for each registration */

RegistrationDetail registrationDetail =

registrationManager.getDetail(r);

count++;

System.out.println("Registration #" + count);

printRegistration(registrationDetail, locator);

System.out.println();

System.out.println(count + " total registrations");

System.out.println("end of viewProfile");

} /* viewProfile */

/**

* Utility method to print out the contents of a registration

| Sample Java Program | 393

* @param detail RegistrationDetail


* @param locator ServiceLocator

*/

private void printRegistration(RegistrationDetail detail, ServiceLocator


locator) throws

SabaException

String regNumber = detail.getRegNo();

Date startDate = detail.getFromDate();

Date endDate = detail.getToDate();

OfferingManager manager = (OfferingManager)

locator.getManager(Delegates.kOfferingManager);

Offering offering = detail.getOffering();

OfferingDetail offeringDetail =

manager.getDetail(offering);

String name =

offeringDetail.getOfferingTemplate().getDisplayName();

System.out.println(regNumber + ": " + name

| Sample Java Program | 394

+ "; startDate = " + mDateFormat.format(startDate)

+ "; endDate = " + mDateFormat.format(endDate));

} /* printRegistration */

/**

* Demonstrates use of PartyManager to modify user data.

* @param locator ServiceLocator instance

* @param employee Employee reference specifying user to update

*/

public void updateUser(ServiceLocator locator,

Employee employee) throws Exception

System.out.println("start of updateUser");

PartyManager partyManager = (PartyManager)

locator.getManager(Delegates.kPartyManager);

/* Obtain the detail information for the employee. */

EmployeeDetail detail = partyManager.getDetail(employee);

| Sample Java Program | 395

/* Update this detail with new details - to indicate the

employee has been fired.

Notice that it is important that we

update an existing detail object, instead of creating a new

detail.

This ensures that the detail's timestamp and other

persistence metadata is correct.

The timestamp is used

internally to prevent stale updates. */

detail.setTerminatedOn(new Date());

detail.setStatus("Terminated");

/* Now actually make these changes on the back-end. */

partyManager.updateEmployee(employee, detail);

System.out.println("end of updateUser");

} /* updateUser */

/**

* Demonstrates use of OfferingManager, et al to create

* a new offering instance.

* @param locator ServiceLocator instance

| Sample Java Program | 396

*/

public void createClass(ServiceLocator locator) throws Exception {

System.out.println("start of createClass");

/* First, create a new ILT Offering. */

System.out.println("creating offering template");

/* Step 1.

Create the offering template.

OTs have an

entity-style interface, so we obtain the home interface and

use it to create a new OfferingTemplate. */

OfferingTemplateHome otHome = (OfferingTemplateHome)

locator.getHome(Delegates.kOfferingTemplate);

/* create the detail defining the new OT */

Domain domain = (Domain)

ServiceLocator.getReference(Domain.class,

"domin000000000000001",

"world");

Timestamp startDate = getDate(2002, 1, 1);

Timestamp endDate = getDate(2002, 12, 31);

| Sample Java Program | 397

BigDecimal price = new BigDecimal(100.00);

String courseNumber = "WH101";

String title =

"Wormhole Navigation";

String description = "Techniques for successful wormhole "

+ "navigation and avoiding cellular decomposition";

OfferingTemplateDetail otDetail = new OfferingTemplateDetail(

domain, // domain

title, // title

description, // description

"Wormholes:

In and Out in One Piece",

startDate, // available from

endDate, // discontinued from

null, // training units

courseNumber, // course number

null, // vendor

true, // available from call center

true, // available from web

// abstract

| Sample Java Program | 398

true, // is published

false); // is test

OfferingTemplate ot = otHome.create(otDetail);

/* Step 2.

Ensure this offering template is available in a

* specific language. */

Language language = getEnglishLanguage(locator);

createDefaultLanguage(locator,language, ot);

/* Step 3.

Create a delivery mode for this offering template. */

System.out.println("creating delivery mode");

Delivery delivery = (Delivery)

ServiceLocator.getReference(Delivery.class,

"eqcat000000000000004",

"Instructor-Led");

String acronym="WH";

DeliveryModeDetail dmDetail = new DeliveryModeDetail(delivery,

ot,

acronym,

| Sample Java Program | 399

domain);

DeliveryModeHome dmHome = (DeliveryModeHome)

locator.getHome(Delegates.kDeliveryMode);

DeliveryMode dm = dmHome.create(dmDetail);

/* Step 4.

Create the offering.

This is an instance

of an offering with a specified delivery type and other

information specific to the type of offering, such as date and

time for an ILT class. */

System.out.println("creating ILT");

OfferingManager manager = (OfferingManager)

locator.getManager(Delegates.kOfferingManager);

String classNumber ="WH101-1a";

Location location = getLocation(kLocationName, locator);

if (location == null)

throw new Exception("Unexpected error in createClass - "

+ "no location found");

startDate = getDate(2002, 10, 1);

| Sample Java Program | 400

endDate = getDate(2002, 10, 2);

Integer maxCount = new Integer(20);

Integer minCount = new Integer(2);

Integer status = new Integer(100); // status = Open - Normal

Integer zero = new Integer(0);

BigDecimal maxDiscount = new BigDecimal(0);

Facility facility = (Facility)

ServiceLocator.getReference(Facility.class,

"fclty000000000001000",

"Hilton Suites");

Integer deliveryType =

new Integer(100); // session based.

Use 200 for

// inventory, 300 for downloadable

Company vendor = (Company)

ServiceLocator.getReference(Company.class,

"cmpny000000000001000",

"UT Explorers, Inc.");

| Sample Java Program | 401

Timestamp openEnroll = getDate(2002, 8, 1);

Timestamp closeEnroll = getDate(2002, 9, 30);

/* create a time period detail to define the session

* templates for this class */

SabaTimeZone timezone = (SabaTimeZone)

ServiceLocator.getReference(SabaTimeZone.class,

"tzone000000000000005",

"(GMT-08:00) Pacific Time (US & Canada), Tijuana");

TimePeriodDetail tpDetail = new TimePeriodDetail(timezone);

/* add an element for each day's session to the time period */

tpDetail.addElement(startDate, startDate, "09:00", "17:00");

tpDetail.addElement(endDate, endDate, "09:00", "17:00");

/* create the session template for this class */

TimePeriodTemplateDetail tptDetail = new TimePeriodTemplateDetail();


tptDetail.setTimeElements(tpDetail.getTimeElements()); SessionTemplateDetail stDetail =

new SessionTemplateDetail("MT9-5", // name

2, // length

WeekDay.kMONDAY, // start day

| Sample Java Program | 402

false, // check start day

tptDetail);

SessionTemplateHome stHome = (SessionTemplateHome)

locator.getHome(Delegates.kSessionTemplate);

stHome.create(stDetail);

ILTOfferingDetail detail = new ILTOfferingDetail(

classNumber, // class number

ot, // offering template

location,

startDate,

endDate,

zero, //maxIntConf

maxCount, // maximum number of students

maxCount, // max book (maximum number + wait list size)

minCount, // minimum number of students

zero, // studCt, count of confirmed bookings

zero, // studBook, count of all bookings (confirmed + waitlisted)

| Sample Java Program | 403

status,

null, // CSR

domain,

stDetail.getName(), // session template

null, // when cancel

language, // language

new Boolean(false), // broadcast

new Boolean(true), // display for web

new Boolean(true), // display for call center

null, // training units

facility,

new Boolean(false), // rescheduled

vendor,

// vendor_id

description,

openEnroll,

openEnroll, // open enroll for all

closeEnroll,

null, // delivery

| Sample Java Program | 404

null, // parent class

new Boolean(false), // is test

null, // price band

null, // price band unit

null, // post order

null, // post completion

null // stop auto promotion

);

/* now create the ILT offering */

ILTOffering offering = manager.createOffering(detail, tpDetail);

/* Step 5:

Add an attachment to the class */

/* attachments have a single header and one or more attachment

- where each attachment proper is keyed to its own locale. */

System.out.println("creating attachment");

SabaLocale locale = (SabaLocale)

ServiceLocator.getReference(SabaLocale.class,

"local000000000000001",

| Sample Java Program | 405

"English");

AttachmentDetail atDetail= new AttachmentDetail(

locale,

"http://www.mapquest.com/maps/map.adp?country=US"

+ "&address=2400+Bridge+Parkway&city=&state=&zipcode=94065"

+ "&homesubmit.x=44&homesubmit.y=16", // url

"Map"); // attachment type

AttachmentHeaderDetail atHeaderDetail= new AttachmentHeaderDetail(

"Map to Classroom",

// description

"text/html", // doc type

true); // is private

/* Use a builder interface to create the attachment.

This is

a delegate to a stateful session bean, which uses a multi-step

process to build up the full description of the attachment. */

/* We begin by using the home interface to specify the

reference (in this case, our new ILT offering) that has

the attachment. */

| Sample Java Program | 406

AttachmentManagerHome attachmentManagerHome =

(AttachmentManagerHome)

locator.getHome(Delegates.kAttachmentManager);

AttachmentManager attachmentManager =

attachmentManagerHome.create(offering);

/* now obtain a builder by specifying the attachment header to

* use with it. */

AttachmentBuilder attachmentBuilder =

attachmentManager.createHeader(atHeaderDetail);

/* now add the attachments proper */

attachmentBuilder.addURLAttachment(atDetail);

/* now commit the changes and free up the builder */

attachmentBuilder.save();
ee /* Now create a WBT. It will inherit from the same offering

* template. */

/* first, create a delivery mode */

delivery = (Delivery) ServiceLocator.getReference(Delivery.class,

"eqcat000000000000005",

| Sample Java Program | 407

"WBT");

acronym = "WH-OL";

dmDetail = new DeliveryModeDetail(delivery,

ot,

acronym,

domain);

dm = dmHome.create(dmDetail);

/* next, create the WBT */

System.out.println("creating WBT");

String wbtNumber ="WH101-OL";

BigDecimal cost = new BigDecimal(150.00);


ee String url = "http://www.uncharted.com/territories/moya.html";

WBTOfferingDetail wbtDetail =

new WBTOfferingDetail(

wbtNumber,

startDate,

endDate,

| Sample Java Program | 408

url,

"", // server path

"10", // available for days

domain,

new Boolean(true), // display for web

new Boolean(true), // display for call center

null, // training units

vendor,

null, // manufacturer

description,

ot,

new Integer(5), // maximum download count

language

);

WBTOffering wbtOffering = manager.createOffering(wbtDetail);

System.out.println("end of createClass");

} /* createClass */

| Sample Java Program | 409

/**

* Builds and returns the specified date as a Timestamp

* @param year int value for year

* @param month int value for month (1-12)

* @param date int value for date (1-31)

* @return Timestamp containing the date

*/

private static Timestamp getDate(int year, int month, int date)

Calendar cal = Calendar.getInstance(Locale.US);

// compensate for 0-based month by subtracting 1

cal.set(year, month - 1, date);

java.util.Date thisDate = cal.getTime();

long time = thisDate.getTime();

return new Timestamp(time);

} /* getTime */

/**

| Sample Java Program | 410

* Utility method to add a default language to the specified

* offering template

* @param locator ServiceLocator

* @param language Language to add

* @param template Offering Template to add the language to

*/

protected void createDefaultLanguage(ServiceLocator locator,

Language language,

OfferingTemplate template)

throws SabaException

CourseLanguageManagerHome home = (CourseLanguageManagerHome)

locator.getHome( Delegates.kCourseLanguageManager );

CourseLanguageManager manager = home.create(template);

CourseLanguageDetail detail =

| Sample Java Program | 411

new CourseLanguageDetail(language, "0000000000");

CourseLanguage cl = manager.addCourseLanguage(detail);

/**

* Utility method to return a reference to the English language

* @param locator ServiceLocator

* @return Language, or null if none found

*/

private Language getEnglishLanguage(ServiceLocator locator)

throws Exception

/* Languages are found using a stateless delegate, i18nManager */

I18NManager manager = (I18NManager)

locator.getManager(Delegates.kI18NManager);

String kLanguageName = "English";

Collection languages =

manager.findLanguagesByName(kLanguageName);

| Sample Java Program | 412

Iterator i = languages.iterator();

if (!i.hasNext()) {

throw new Exception("Unexpected error:

+ "English language not available.");

Language language = (Language) i.next();

return language;

} /* getEnglishLanguage */

} /* APIExamples */

"

Chapter

14
Security Configuration
Topics:

Overview
Notes on Saba Configuration
Using Alternate Login Modules
Web Server Single-Sign On
J2EE Single Sign-On

This chapter describes some of Saba's advanced security options.

| Security Configuration | 414

Overview
Saba provides a highly flexible security infrastructure by conforming to as many standards as possible. Using the JAAS
standard provides customers the option to use pretty much any authentication mechanism they choose. By conforming
to J2EE web based security, we can support single sign-on between two unrelated J2EE web applications.
However, in order to provide the extensive security features demanded by our enterprise customers, we provide a more
granular security mechanism through our proprietary security layer wrapped in the J2EE standards.
The following sections give an overview of the Saba specific features.

Notes on Saba Configuration


The following sections describe some special considerations in configuring Saba security.

Special Users
There are two special Saba users: admin and guest. Each has a specific purpose.
admin is designed as a bootstrap super user. This particular user is intended solely as a system-level user that allows a
customer to always access the system in super-user mode. It is strictly for:

Creating "real" users and granting them any privilege.


Navigating to the SabaAdmin servlet to configure machine level information.

admin is not really intended to perform any normal administrative activities. Customers are expected to designate explicit
administrators to correspond to their business requirements.
The guest user is the unauthenticated user. It is used to initially enter the system.
Neither of these users exists in the person schema in Saba's database.

Saba J2EE Roles


We currently define the J2EE Roles in the chart below:
Role Name

Description

Members

sabauser

Any authenticated user and guest

All Saba users, including guest

sabaadmin

The administrator super user.

The admin user

guest

Un-authenticated users

The guest user

| Security Configuration | 415

Security.xml
Security related options are configured in [Saba install directory]/properties/security.xml. The following
chart explains what each property is and when it is used.
Property Name

Description

Used When

useCompositePrincipalName

True indicates that JAAS Principal Must set it to false when:


name uses a format "siteName|loc~
Using 3rd party LoginModule, or
ale|username" instead of "user~
name". Setting it to false implies Using single sign-on.
that only the default site can be
used, and that a single username
(for example, "guest") cannot have
different preferred locales in two
concurrent sessions. Can still sup~
port multiple desktops and user can
still modify his preferred locale,
however.

useSabaLoginModule

When true, Saba provides ad~


Set to false when using ANY LoginModule
vanced password management
instead of com.saba.auth.SabaLoginModule
functionality. Setting to false, dis~
ables that functionality because
Saba can only control password
when authentication is done with
SabaLoginModule

support42Certificates

When set to true, Saba accepts


certificates issued in Saba 4.2.

This is only useful if upgrading from 4.2 and


users have offline content that they have not
yet submitted. If you must set it to true, be
sure to do so only until offline 4.2 content is
submitted. For security reasons, this really
should be set to false.

There are several implications of setting useCompositePrincipalName to false:

Setting it to false implies that only the default site can be used. Saba implements multi-site functionality using a
composite principal. Multi-site functionality is not available in the J2EE standard.
A single username (for example, "guest") cannot have different preferred locales in two concurrent sessions. However,
can still support multiple desktops and a user can still have a distinct preferred locale.
Saba's proxy user feature is disabled.

Login Modules
Login Modules implement a standard interface (defined by JAAS) that let you easily configure different authentication
mechanisms for applications.
Saba ships with three Login Modules that you can use: com.saba.auth.SabaLoginModule,
com.saba.auth.standard.LDAPLoginModule, and com.saba.auth.standard.NTLoginModule.

| Security Configuration | 416

SabaLoginModule uses the username and password stored in Saba's database to authenticate users. It is the default
login module. LDAPLoginModule can be used to authenticate against an LDAP server. (This includes Microsoft's
Active Directory, which can act as an LDAP server.) NTLoginModule can be used to authenticate using the NT or
Windows 2000 operating system.
In addition to these modules, you can also use any third-party login module or write your own custom one.

Public and Private Keys


Each instance of Saba generates its own Public and Private Key pair for cryptographic work. These keys are stored in
[Saba install directory]/properties directory as PublicKey.der and PrivateKey.der respectively. If you want
certificates issued from one installation to be accepted by another, you should make sure that both installations share
the same key files. You can copy the pair from one machine to another.

Using Alternate Login Modules


To use any alternate Login Module you must configure Saba Enterprise to use it instead of SabaLoginModule.
If you want to use a different LoginModule than com.saba.auth.SabaLoginModule, follow these steps:
1. Log on to Saba as the system administrator. Open the System Administration module.
2. Open the System Administration->System Configuration->System->System Level Properties screen, and click
the Security link.
3. Set the Login Module property to the fully-qualified class name of your login module.

WebServerSSOLoginModule
This is the default authentication method. The Web Server Secure Sign-On (SSO) module uses the web server to create
a trust relationship with the user's browser. If this module is used, then a user who is properly logged in (and authenticated)
to the client computer is automatically logged in to Saba. The web server uses the NT Challenge/Response protocol to
securely verify that the user is authenticated on the client.
This module is discussed fully in "Web Server Single-Sign On".

LDAPLoginModule
This Login Module enables generic LDAP authentication while still maintaining multi-site functionality. In other words,
useCompositePrincipalName in Security.xml can be true.
This implementation communicates with the LDAP server using JNDI. Configuration of this adapter is done in the
[saba.install.path]/web/[site]/properties/ldap_auth.properties file.
The JNDI options include whatever options your LDAP JNDI provider supports. The mapping of JNDI options to the
property names in ldap_auth.properties is:
JNDI Property

Property Name in ldap_auth.properties

Context.INITIAL_CONTEXT_FACTORY

initialContextFactory

Context.SECURITY_PROTOCOL

protocol

| Security Configuration | 417

JNDI Property

Property Name in ldap_auth.properties

Context.PROVIDER_URL

providerURL

Context.SECURITY_AUTHENTICATION

authenticationScheme

To authenticate a given user, this implementation constructs a distinguished name (DN) by either first connecting using
a default JNDI context and looking it up, or by using the following formula:

principalDNPrefix + username + principalDNSuffix


Which method it uses depends on the value of the lookupUserDN property. After constructing a DN, it attempts to
connect to LDAP using that DN and the specified password.
If lookupUserDN is true, then it must connect to the LDAP server to lookup a user's
DN. The DN used to connect to the server to perform that lookup is calculated using "principalDNPrefix + adminUsername
+ principalDNSuffix", and the passwordis adminPassword.
Additional properties include:

uidAttributeName: The name of the attribute that in the object containing the users that corresponds to the userid.
userContextDN: Contains the LDAP context from which to search for users when lookupUserDN is true.

Note that colons and equal signs are escaped using a backslash in the file, but not when viewed in SabaAdmin.
A sample properties file looks like this:
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
protocol=
providerURL=ldap\://ldapServer\:389
authenticationScheme=simple
principalDNPrefix=uid\=
principalDNSuffix=, ou\=People, dc\=sampleco
lookupUserDN=false
adminUsername=
adminPassword=
userContextDN=ou\=People, dc\=sampleco
uidAttributeID=uid

| Security Configuration | 418

NTLoginModule
This LoginModule enables authentication the NT and Windows 2000 operating systems. This can be used in place of
the default SabaLoginModule.
It works by placing sspauth.dll in a directory in the system path, and setting NTDomain property in "Web Utility
Variables" in SabaAdmin to a comma delimited set of domains to check for valid users.
For example, if the saba users were members of two domains, SALES and MARKETING, then you would set
NTDomain=SALES,MARKETING
If a user attempts to log in as "userone", this LoginModule checks the password against the NT users
"SALES\\userone" and "MARKETING\\userone".
Note that this is different from com.sun.security.auth.module.NTLoginModule. This login module passes
username and password to Windows for authentication, whereas Sun's authenticates the currently running NT user.

Using a Third Party Login Module


Using a third party login module (such as one that comes with your application server) is possible. However, you must
set useCompositePrincipalName to "false" in security.xml. Please consider the limitations of that setting
as described in the section on security.xml before setting it to false.
Some LoginModules written especially for a particular Application Server do more than authenticate-in some cases they
also tell the EJB Server what the Principal returned by EJBContext.getCallerPrincipal should be and provide J2EE role
mappings. Any alternate login module should not tell the EJB server what principal to use. However, it can assign
additional roles, but the SabaRoleMapperLoginMapper maps all needed Saba roles.

Writing a Custom Login Module


Any custom LoginModule works with Saba. However, if you wish to support useCompositePrincipalName set to "true"
in security.xml, then the custom LoginModule must extend com.saba.auth.common.BaseLoginModule,
and implement the verifyUser method.
To indicate that the username or password is invalid, then the verifyUser method must throw SabaFailedLoginException.
Returning without throwing an exception indicates that the username and password combination are valid.

Web Server Single-Sign On


The Web Server Single-Sign On option uses the web server to create a trust relationship with the client's browser. Saba
can use this to verify that a particular user is properly authenticated on his or her client machine, so the user doesn't have
to enter a user name and password to log in to Saba.
Microsoft's Internet Explorer uses a proprietary protocol known as NT Challenge/Response (also known as NTLM) to
pass the Window's authenticated user information to Internet Information Server (IIS). Recent versions of Netscape's
browser (7.1 and later) and Apache web server also support NTLM. This system is more secure than HTTP's Basic
Authentication protocol because communication is encrypted, and it is user-friendlier because the end-user does not
need to re-enter his username and password when connecting to the web server.

| Security Configuration | 419

How It Works
Saba implements Web Server Single Sign-On by integrating several Active Server Pages (ASP), a login page that
automatically submits itself, and a JAAS login module, WebServerSSOLoginModule:

1. The HTTP Client issues a request for the J2EE web resource /Saba/Desktop. This request goes through IIS, and
the plug-in proxies any responses from the application server.
2. The application server sees that this web resource is protected by a security constraint in Saba's web.xml and that
the client is not yet authenticated with the application server. It sends a redirect to the client to go to the login page
defined in web.xml.
3. With other authentication mechanisms, the login page lets the user enter in a username and password so that the
client can submit these values and authenticate with the application server. With the Web Server Secure Sign On
authentication module, the login page redirects to authenticator.asp.
4. authenticator.asp creates an IIS session if there isn't already one, and saves the username of the user running
the HTTP client in its session object. (IIS gets the username by using NTLM.) It then sends an HTML form back to
the client, which on loading automatically submits itself to /Saba/j_security_check and passes the Window
client's username (in the j_username field) and the IIS session id (in j_password).
Note: The contents of j_username and j_password may include additional or different information as
well, but we can ignore it for the purposes of this example.
5. The application server then passes this username and password pair to the registered login modules. When the
WebServerSSOLoginModule gets this username/password pair, it submits a request to authorizer.asp on IIS
to confirm that this username is the one stored in the session object for the specified session id. If they match, the
login module confirms that the authentication was successful.
6. After a successful authentication, the application server redirects to the original request, only now the user is properly
authenticated.

Setup and Configuration


To set up Web Server Secure Sign-On, you must take the following actions:
1. Install the two ASP pages and configure IIS.
2. Configure the application server to use WebServerSSOLoginModule.
3. Modify Saba's "Web Server Single Sign-on" properties to match the user's environment.

Configure IIS
IIS must front the application server. For information about this, consult the application server's documentation.
Next, install the two ASP pages and set the security privileges for those pages correctly:
1. Create a new virtual directory using Internet Services Manager that can run scripts called "sso". Make sure the "Read"
and "Run Scripts" access permissions are checked.

| Security Configuration | 420

2. Extract the pages authenticator.asp and authorizer.asp from saba.ear and copy them to the virtual
directory created. For example,
webpackager extract SabaSite\index\j2eesecurity\authenticator.asp
C:\temp
webpackager extract SabaSite\index\j2eesecurity\authorizer.asp
C:\temp
cd /d C:\temp
copy SabaSite\index\j2eesecurity\*.asp C:\Inetpub\sso
3. Set the security privileges of authenticator.asp to use Challenge/Response authentication. To do this:
a.
b.
c.
d.

Click on the sso virtual directory in Internet Services Manager


Right-click authenticator.asp, and select Properties
Click on the File Security tab
Click the Edit button in the authentication control section. Make sure that Anonymous access is not checked,
and Integrated Windows authenticationis checked:

4. Set the security privileges of authorizer.asp to accept anonymous connections. Note that this is the opposite
of the setting you used for authenticator.asp. To do this:
a.
b.
c.
d.

Click on the sso virtual directory in Internet Services Manager


Right-click authorizer.asp, and select Properties
Click on the File Security tab
Click the Edit button in the authentication control section. Make sure that Anonymous accessis checked, and
Integrated Windows authenticationis not checked:

5. Customize authenticator.asp to match your system. Open up authenticator.asp with a text editor,
and modify the parameters at the top- sabaAppContext and thisMachine- to match your system.
sabaAppContext is the application context used when deploying saba.ear, which is "/Saba" by default.
thisMachine is the host name that the login module uses to connect to this IIS machine.

Using WebServerSSOLoginModule
The default installation of Saba configures the application server to use the WebServerSSOLoginModule. For more
information about configuring login modules, see "Using Alternate Login Modules".

Configure Sign-On Properties


The final step is to use the Saba Administration module to configure the sign-on options:
1. Lanch Saba. Go to the System Administration->System Configuration->System->Web Server Single Sign-On.
2. Set Web Server SSO Enable to true.
Note: Once you make this change, you may be unable to log in to Saba if you are forced to log in before you
finish configuring Web Server Single Sign-On. If you have this problem, you can disable SSO by opening the
file install_dir/properties/singlesignon.properties, and setting the property ssoenable=false

| Security Configuration | 421

3. Set Trusted Servers to list the servers which the WebServerSSOLoginModule can trust. This should be set to
a comma-delimited list of server names. In web server cluster configurations, explicitly list each web sever host
here. If there is only one web server, then enter that one host name. Note that the names must match the names set
in the theMachine variable in authenticator.asp. (as described above in "Configure IIS".)
4. Set Trusted Domains to list the Windows domains the login module can trust. Any authentication attempts fail for
a user who does not belong to one of the listed domains.
5. Set Web Server Protocol to the protocol that the login module uses to communicate with the Authorizer Page. If
the browser client is using SSL, it is not necessary for the login module to also use SSL unless IIS is configured to
only accept SSL connections, or secure session IDs are used. However, if Web Server Protocol is set to https://, the
login module establishes a different SSL connection to IIS than the client uses. Thus it is impossible to use SSL with
secure session IDs and this single sign-on solution. Secure session IDs should not be necessary as long as all links
used in Saba use SSL to communicate with the server.

J2EE Single Sign-On


Single sign-on can mean different things to different people. Here are two possible definitions:

Two different applications (for example, an operating system and Saba) share a common authentication mechanism.
The same userID/password combination works for both applications.
Two different web applications (for example, a Sales Force Automation (SFA) application and Saba) share a common
authentication mechanism AND session information. A user logs in once and can access both applications and Saba
in a single browser session without having to log into each application separately.

To do the former, read about using alternate Login Modules discussed above. This section describes how to accomplish
the latter. To be successful, all applications sharing session authentication information must be fully J2EE compliant.
In particular, this means supporting web based security settings in the web.xml deployment descriptor and supporting
JAAS.

Disable Composite Principal


In order to share authentication information between J2EE web applications, they must share a common principal name.
Therefore, you must set useCompositePrincipalName to false as described in the section on security.xml.
You may follow the above instructions for configuring an appropriate Login Module that works for both applications.

Add Common Roles to Web Descriptor


Make sure that security-role elements used by both applications are declared in the web.xml deployment descriptor of
each application. Otherwise, users in some cases are authenticated but do not have the proper roles.

| Security Configuration | 422

To do this, look in the web.xml deployment descriptor of saba.ear to get the complete list of roles required by saba.
Copy and paste those into other application's web.xml. Do the same for all of the other application's roles. For example:
<security-role>
<description>Guest privileges to the Saba application</description>
<role-name>guest</role-name>
</security-role>
<security-role>
<description>Any valid Saba user including guest</description>
<role-name>sabauser</role-name>
</security-role>
<security-role>
<description>Saba Administrator</description>
<role-name>sabaadmin</role-name>
</security-role>

Security Constraints
The servlet spec does not specify what happens to currently authenticated user if that user navigates to a URL that is
not protected by a security-constraint element in web.xml. Some application servers drop the authenticated user entirely.
It is therefore important that all URLs are protected with a security constraint. By default, this is the case with Saba.

Chapter

15
Eclipse Plug-in
Topics:

Overview and Requirements


Installing and Configuring the
Plug-in
Editing WDK Pages

This chapter describes the Saba plug-in for working with WDK pages in Eclipse.

| Eclipse Plug-in | 424

Overview and Requirements


As described in Chapter 3, "Scripting Customization (WDK)"Chapter 3, "Scripting Customization (WDK)", Saba
application pages are written in XML. In fact, a Saba application page is constructed from three separate XML files:

The control file is the master file. It specifies the other two files used by the application page, assigns a unique
identifier number to the page, and specifies certain other high-level resources (such as the widget library used by
the page).
The model file specifies the data displayed in the application page, and also specifies which widgets are available to
the page.
The view file specifies how the data and widgets are arranged on the application page.

All three files are written in XML. You can write Saba application pages with any XML editor or with any text editor.
However, as a convenience, we have provided a plug-in to make it easier to write and edit WDK pages from inside
Eclipse, a popular third-party application development environment. This plug-in makes Eclipse's built-in text editor
more useful for working with Saba application pages.
Note: The plug-in supports only Eclipse versions 2.1.2, 2.1.3, and 3.0. The plug?in does not support any other
versions of Eclipse.

Installing and Configuring the Plug-in


The Eclipse plug-in is easy to install and configure. When the files are installed into the proper location, Eclipse
automatically detects them and installs the plug-in the next time it is launched.

Installing
The plug-in is provided in the developer directory of the product distribution as a ZIP archive. This file has a name like
com.saba.wdk.xmleditor_version_number.zip
To install the plug-in into your Eclipse installation, follow these steps:
1. Shut down Eclipse.
2. Use any ZIP file editor to open the archive. Install the archive's contents into your Eclipse installation directory's
plugins directory. (For example, if you installed Eclipse into d:\Eclipse, you would extract the archive into
d:\Eclipse\plugins.) Make sure your ZIP extractor creates the subdirectories specified by the archive. There
is probably a setting with a name like "Create Subdirectories" or "Use folder names".
3. Verify that the files were installed properly. The ZIP extractor should have created a subdirectory of plugins
named com.saba.wdk.xmleditor_version_number.
4. Restart Eclipse. Eclipse should automatically detect and install the plug-in. You can verify that the plug-in has been
installed by opening Window->Preferences. There should be a new Preferences section named WDK Editor.
If you ever need to reinstall the plug-in, shut down Eclipse, delete the old
com.saba.wdk.xmleditor_version_number directory, then follow the installation steps described above.

| Eclipse Plug-in | 425

Setting Up the Project


The Eclipse plug-in enables you to edit any WDK files in any of your Eclipse projects. If you do not have an Eclipse
project containing the WDK files, you must create one. You can create either a Simple or a Java project, depending on
your needs. (For example, if you are developing Saba-related Java code in parallel with WDK application pages, you
might want the pages and the Java code to all be in the same Java project.)
This section describes the steps you takes to set up a typical Eclipse project for editing WDK pages. This section contains
rough guidelines. You may choose to modify them.
To set up an Eclipse project, follow these steps:
1. Create a new Eclipse Java project. Choose an appropriate name (for example, customPages and location (for
example, d:\customPages).
2. Edit the Project properties. Select the Java Build Path property set and the Libraries tab, and click the Add External
JARs... button. Add the following libraries from your Saba installation:
<saba install directory>\lib\saba.jar
<saba install directory>\lib\sabasecurity.jar
<saba install directory>\res\sabares.jar
3. Extract the contents of the archive clientSource.zip into a "java" directory within the project that you created
in step 1. This makes the Java source code used by the application's web pages available within your project. You
can find clientSource.zip in your Saba distribution in the disc1\toolkit\developers\source
directory.
4. Use Saba's WebPackager utility to extract Saba's stylesheets into your custom directory. These stylesheets are
included by any custom pages you author. This is usually done with a command like

webPackager extract SabaSite\wdk\xsl\view d:\customPages


The WebPackager utility is discussed in detail in "Extracting WDK Files with WebPackager".
5. Use the WebPackager utility to extract the web pages you want to customize. For example, to extract all the learning
pages, you might use a command like

webPackager extract SabaSite\learning d:\customPages


Again, the WebPackager utility is discussed in detail in "Extracting WDK Files with WebPackager".
6. Configure the WDK editor plug-in, as described in "Configuration" below.

Extracting WDK Files with WebPackager


In order to work with the Saba WDK pages, you must extract Saba's stylesheets, as well as the specific WDK pages you
edit or copy. These files are part of the Saba installation. You can extract them with the webPackager script, which
is installed into your site's SabaWeb\Bin directory. (Two versions of the script are installed: webPackager.bat,
for use on Windows servers, and webPackager.sh, for Unix servers.)
The webPackager script can perform a wide range of functions. This section describes one function: using the script
to extract files from the Saba installation and copy them to the location you choose.
To use the webPackager script, follow these steps:

| Eclipse Plug-in | 426

1. Open the appropriate environment script found in the same directory as webPackager. (That is, if you are on
a Windows machine, open environment.bat. If you are using Unix, open environment.sh.) Verify that
the various environment variables are set appropriately for your system.
2. Call the webPackager script with the appropriate parameters. To extract files from the Saba installation, use the
following syntax:

webPackager extract saba_dirextract_location

saba_dir: The location in the Saba resource of the files you want to extract. For example, stylesheets are located
in SabaSite\wdk\xsl\view; application pages are stored in various subdirectories of SabaSite
extract_location: The directory where the extracted files should be installed. webPackager creates any needed
subdirectories.

Thus, the command to extract all the stylesheets on a Windows installation and install them to d:\myCustomPages
would be

webPackager.bat extract SabaSite\learning d:\myCustomPages


The command to extract all the Saba Performance pages on a Unix installation and install them to /customDev would
be

webPackager.sh extract SabaSite/performance /customDev

Configuration
There are some configuration options that you must set:

The plug-in associates RDF files with itself. (Saba control files use the extension .rdf.) If RDF files are not already
associated with another program, you can use the Saba WDK plug?in by opening an RDF file with Eclipse. If you
have already associated RDF files with another program, however, that program takes precedence. To change this,
follow these steps:
1.
2.
3.
4.
5.

Open the Eclipse Preferences window (by choosing Window->Preferences)


Navigate to the Workbench->File Associations preferences section
Choose the *.rdf file type
Select the WDK Page Editor in the Associated Editors section
Click Default to set that editor as the default

If you do not want to make Saba's plug-in the default editor, you can still use it by right-clicking an RDF file and
choosing Open With->WDK Page Editor.

You may also wish to associate XML files in general with the plug-in. If you do this, you can open model files
directly, instead of having to open the control file which uses the model file. To do this, follow these steps:
1.
2.
3.
4.

Open the Eclipse Preferences window (by choosing Window->Preferences)


Navigate to the Workbench->File Associations preferences section
If *.xml is not listed as a file type, click on Add... and add it as a new file type.
Select the *.xml file type, and check if the WDK Modelpage Editor is listed. (Note that the editor for XML
files has a different name than the editor for RDF files.) If it is not, click the Add... button next to the Associated
Editors panel, and select WDK Modelpage Editor from the Internal Editors list.
5. If you wish, you can make the plug-in the default editor for XML files by selecting WDK Modelpage Editor
and clicking the Default button.

| Eclipse Plug-in | 427

You must configure your Saba site to load the project's custom pages. To do this, follow these steps:
1. Log in to Saba with an account with system administration privileges. Open the Saba System Administration
module. Navigate to the screen System Administration->System Configuration->Sites.
2. Select the site you wish to configure.
3. Open the Web Variable screen for that site.
4. Set the Alternative Source URL property to the value
<project_path>/SabaSite
where <project_path> is the URL of the root of your Eclipse project. The URL should use the file:/ prefix,
indicating that files are loaded from the local file system. For example, if your project stores its files in
C:\customPages on the Saba server, you would set the Alternative Source URL property to the value

file:/c:/customPages/SabaSite

There are several Preferences settings for the WDK plug-in. If you want to use the Preview functionality (described
in "Preview Pane"), you must edit one of these. You can edit the preference settings by opening the Eclipse
Preferences window (by choosing Window->Preferences), and selecting the WDK Editor section.
The available preferences settings are:
WDP Page Editor preference settings
Field

Description

Mode

Indicates how the Eclipse plug-in should run. Most people should use Customizer (indicating
that the plug-in is customizing an existing Saba installation).
This documentation assumes you are using the Customizer mode. It does not support using
the Developer mode (intended for people developing and compiling the Saba application).

Schema URL

The location of the XML schema describing Saba model pages. The plug-in uses this to find
the documentation for model page nodes. Usually, you should set this to the URL of the
documentation XML file in your saba.jar file, such as:

resource:com/saba/wdk/xmleditor/documentation.xml
However, if this URL is not valid, the plug-in uses the copy of the schema stored in the
plug-in's JAR file. (However, this may result in the plug-in using documentation which is
out of sync with your installation.)

Application URL

If you want to use the Preview functionality (described in "Preview Pane"), enter the URL
of your application server in this field. For example, if Saba is installed on a host named
sababox, this might be

http://sababox/Saba/Web/Main

Temp directory

Location for temporary files. This must be a full, filesystem path.

| Eclipse Plug-in | 428

Field

Description

Asset URL

Location of Saba static assets (graphics, etc.), expressed as a URL. If you have a running
Saba installation, this is usually http://server_name/Saba/assets.

Custom bundle root


path

Path to the directory where the plug-in writes custom resource files. These files are created
when you modify the labels for a WDK page, as described in "Labels View".
This path is relative to the Eclipse project root.

Locale

Locale being edited. This is used by the label view.

Location of saba.jar

Full path of the Saba.jar archive. The plug-in retrievees resource information from this
bundle. This archive is found in the Saba installation's lib directory. Thus, a typical value
for this field might be d:\SabaWeb\lib\saba.jar

WDK page directory Path to directory where extracted WDK pages are stored. This path is relative to the Eclipse
project root.

Editing WDK Pages


The Saba plug-in makes it easier to edit WDK pages. To edit an application page, double-click the control file in the
project window (control files have the extension .rdf). The plug-in opens the control file with its text editor, and also
opens the model and view files associated with that control file.
Note: If RDF files are associated with some other program, the Saba plug-in is not launched. If necessary, you
can change the file association by following the steps described in "Configuration". You can also launch the plug-in
by right-clicking on the control file and choosing Open With->WDK Page Editor.
The WDK page editor has five panes:

Control: The control file you opened.


Model: The model file associated with that control page. This pane provides several features to make it easier to edit
Saba pages. These features are described in detail in "Model Pane".
View: The view file associated with that control page. The file is color-coded, and the editor window offers some
code-assist features. For example, when you start typing an XML tag, the window shows a pop-up menu containing
appropriate tags for that context.
Model Object: A Java editing window containing the source code for the application page's model object.
Note: This pane is only present if the application page's control file defines a model object, and if the model
object's source code belongs to the current Eclipse project.

Preview: Used to show the edited application page. This is described further in "Preview Pane".

To edit any file, open the appropriate pane and edit it normally. The WDK editor behaves as a normal XML editor. For
example, when you type an opening XML tag (like <i>), the editor automatically inserts the corresponding ending tag
after the cursor (in this case, </i>). In addition, its Model pane provides extra functionality, which is described below.

| Eclipse Plug-in | 429

Note: If you choose File->Save while a WDK page is open, all files associated with the page are saved, not only
the file in the currently active panel. For example, if you open a WDK page and edit its control pane, then switch
to the model pane and edit that, then choose File->Save, both the control file and the model file is saved.
The Eclipse plug-in also provides a Labels window, which you can use to view and edit the labels used by the application
page. If you edit the labels, the plug-in creates a new Java resource containing only the edited labels. You can install
this resource to change the labels in your application. The Labels view is discussed thoroughly in "Labels View".
You can also use the plug-in to open model files directly, instead of opening the control file which uses the model file.
(This can be useful it you want to extract and modify a model file, and don't need to make changes to the control and
view files for that application page.) To do this, you must associate the WDK plug-in with *.xml files (as described
in "Configuration"). If the WDK plug-in is the default editor for *.xml files, you can edit them by double-clicking
them from the project view. If it is not the default editor, you can still launch it by right-clicking on the model file and
choosing Open With->WDK Modelpage Editor. If you open a model file directly, all the functionality described in
"Model Pane" is available. However, the plug-in does not show a Control, View, or Preview pane.

Model Pane
The model pane enables you to edit the application page's model file. This pane functions as a regular text editor, with
several additional features:

Code Assist: Shows appropriate XML tags for whatever context you're in.
Documentation View: Lists the appropriate child nodes for whatever context you're in, along with a brief description
of each node.
Outline View: Lists all the widgets in the model page in a hierarchical view.

Code Assist
While you are editing a model file, the WDK editor can show you appropriate tags for whatever context you are in. This
only happens when you are inside the file's <wdk:page> node.
If you type a < character while inside the <wdk:page> node, the WDK editor shows a pop-up list of tags that can be
used in your current context. For example, if the cursor is inside a widget node and you type <, the editor shows a list
of child nodes which can belong to that widget.
Note: Some valid child nodes are not displayed in the list. This is a known defect of the editor. If a child node's
tag is not listed, type it in.
If you select a node from the list, the editor inserts it and the node-closing tag into the document. For example, if you
choose <action> from the list, the editor inserts the text <action></action> at the cursor's location, and put
the cursor between the two tags.
The model pane also provides code-assist for Java elements within the model page. For example, when you type the
name of an object or class, followed by a period, the pane shows a pop-up menu listing methods provided by that object
or class. In addition, if you right-click on the name of a Java method and choose Open Declaration, the plug-in opens
a Java window containing the Java code in which that method is declared.
In addition, both XML and Java elements are color-coded to make them easier to read.

Documentation View
The WDK editor provides a Documentation view for use when editing model files. To open this window, choose
Window->Show View->Other, then select WDK Views->Documentation View.

| Eclipse Plug-in | 430

If your cursor is inside the <wdk:page> node, but is not inside an XML tag, the documentation view lists the child
nodes which can be used at your current location. (This is the same as the list of tags which would pop up if you typed
a <.) In addition, the window shows a brief description of each tag.
Note: There may occasionally be valid child nodes which are not displayed in the list. This is a known issue with
the editor.

Outline View
While you are editing a WDK page, the Eclipse Outline view displays the hierarchy of widgets in the page's model file.
(It displays this regardless of which pane you may have displayed.) The outline view shows the XML hierarchy headed
by the model file's <wdk:widgets> node. However, the outline view ignores any <xsp:logic> tags in the file. It
does not ignore the contents of those tags. It acts as if the <xsp:logic> tag was not there, and as if that tag's children
were children of the <xsp:logic> node's parent.
For example, suppose your model file has the following hierarchy:
<wdk:widgets>
<wdk:link />
<xsp:logic>
<!-- some Java code... -->
<wdk:file />
<!-- more code... -->
<wdk:tree />
<xsp:logic />
</wdk:widgets>
In this case, the Outline view shows a <widget> node with three children, the link, file, and tree widgets.
You can further expand any node in the outline view to see its child nodes. The outline view shows all XML attributes
of each node. It does not, however, show the contents of the nodes. That is, it does not show the text between XML tags,
it shows the tags themselves.
If you click on any node in the outline view, the model pane scrolls to the corresponding code.

Preview Pane
The Eclipse plug-in can display the application page in a preview pane. There are two preview modes:

Static: The plug-in uses the assets URL to generate a static representation of the page. This is the default mode.

To view a static representation of a page, open the WDK page and click the Preview tab at the bottom of the window.
A static preview of the page appears, showing you the location of all the widgets on the page.

| Eclipse Plug-in | 431

Dynamic: The plug-in dynamically compiles the page and shows you a running instance, using the Saba application
server. To view the dynamic preview, open the Preview pane and click the Dynamic Preview button (which looks
like the Saba "S" logo):

To enable the Dynamic Preview button, follow these steps:


1.
2.
3.
4.

Select the Custom Perspective option from the Windows menu.


Open the Commands tab.
Select Saba WDK Plugin Action Set from the list of Available Command Groups.
Click the OK button.

To use dynamic preview, you must also configure the plug-in to use the appropriate application server, as described in
"Configuration". In addition, you must be editing the application page that is used by that application server, rather than
a copy of the page stored somewhere else.
When you launch a dynamic preview, the plug-in may prompt you for your Saba user ID and password. After you enter
this, it displays the page that you are editing.
Note: Dynamic preview shows only the modified application pages if you have configured Saba's Alternative
Source URL property to point to the location where the edited application pages are stored. Otherwise, the preview
pane displays the application pages stored in the saba.war file.
When you first launch a dynamic preview, it shows the Saba login page. After you log in, you can navigate to the location
of the page you are editing. The preview pane shows the most recently saved version of the page. Thus, if you make
edits to the page and save it, you can reload the preview pane to see the newly-saved version of the file.
Note: If your application server uses cached versions of the page, the preview pane may not show the most-recently
saved version. Consult your application server's documentation for information about disabling page caching. You
can always force the server to use the current version of the page by shutting it off and restarting it.

Labels View
The labels view enables you to see and change the labels associated with the application page.
The labels view assumes that you follow a particular naming convention. Every control file has an ID number, in its
<rdf:Description> node's id tag. The plug-in assumes that all of an application page's labels have names of the
form

kI18n<page ID #><label name>


That is, if an application label has the ID number 12345, the plug-in assumes that all labels used in the page begin with
the sequence kI18n12345. Thus, the labels might have names like kI18n12345Save, kI18n12345Cancel,
etc.
The label view lists all labels in the Saba resource bundle whose keys begin with the appropriate sequence. (It lists them
whether or not the label is used in the application page.) It also lists the value for those labels, in whatever locale you
specified in the preferences page (as described in "Configuration").
You can edit the labels directly from the label view. To modify an existing label, click on the label's key, then on the
label's value. This places the cursor in the value field, where you can edit it normally. To create a new label, click on
the --NEW key-- line, and type the name of the new label. You can also create a new label by modifying the name of
an existing label's key. To delete a label, delete its key from this window. However, see the note below about the effects
of deleting labels.

| Eclipse Plug-in | 432

Note that editing labels from this window does not make changes to the resource installed in saba.jar. Instead, when
you make edits to the label view, the WDK editor creates a new resource file, in the custom root directory you specify
(as described in "Configuration"). It follows these rules for modifying labels:

If you modify a label which exists in the saba.jar resource, but does not exist in the custom resource, the plug-in
creates an entry in the custom resource file containing the key and value you specify. (If necessary, it creates the
custom file.) The original value of the label is unchanged in the resource in saba.jar.
If you modify a label which already exists in the custom resource file, the plug-in changes the value in the custom
file to the value you specify.
If you create a new label by clicking on --NEW key-- (or by modifying the key of an existing label), the plug-in
makes an entry for the key in the custom file.
If you delete a label which exists in the custom file, the plug-in removes its line from the custom property file.
Note: If the label exists in both the saba.jar resource and the custom resource, it is deleted only from the
custom resource. The window then shows the label with the value it has in saba.jar, the way it does with
every other label which is found in saba.jar and not in the custom resource.

If you try to delete a label which exists in saba.jar but not in the custom resource, it has no effect. Because the
resource in saba.jar is not changed, the window retrieves the label from the default resource bundle again.

Once you have finished making changes to the labels, follow these steps to install them to your Saba installation:
1. Create a ZIP file containing the custom resource files. You can do this with any ZIP utility. Make sure to preserve
folder names. The resource files are in a directory in the custom bundle root path you specified (with the Preferences
window, as described in "Configuration").
Note: While creating the zip file make sure that path of all resource bundles in the zip file starts with /res.
For example: /res/com/saba/client/res/common_labels.properties.
2. Navigate to your Saba installation's bin directory. (For example, if Saba is installed in your server's d:\SabaWeb
directory, navigate to d:\SabaWeb\bin.)
3. Launch the command runPatch.bat (Windows hosts) or runPatch.sh (Unix hosts) from that location. Use
the following command options:

runPatch.bat -p <custom_resource_bundle> -l <log_file>


Parameter

Description

-p

The localization patch file, including the path if necessary. For example,

-p d:\temp\custom_resource.zip

-l

Provide a name and location where runPatch can write a log file. For example,

-l d:\temp\saba_de_AT_patch_install.log
If that file already exists, it is overwritten.

4. When the script has finished, restart the Java application server.

| Index | 433

Index
<$nopage>i18nsee internationalization 15
<$nopage>platformsee application platform 14

A
application platform 14
3-tier architecture 14
application platformdescription 14
architecture 14, 15
common business objects 14
diagram 15
architecturedescription 14
architectureinternationalization 15
architecturesecurity 15
attachments widget 66

I
IM presence widget 154
initializing widgets from request object 60
input widget 155
instant messaging presence widget 161
internationalization 309
list of localizable objects 309
internationalizationdescription 15
internationalizationformat localization support 15
internationalizationlocalizable objects 16
internationalizationmutliple language support 15

J
JavaScriptusing a widget to insert into application pages 213

B
bread crumb widget 68

C
chart toolbar widget 77
chart widget 70
common business objects 14
description 14
component picker widget 81
component widget 84
contact information xi
custom fields widget 89

L
link widget 162
linksdelete link widget 94
linksframework link widget 147
linkslink widget 162
linkssave link widget 212
list of values (LOV) picker widget 184
list widget 178
localizable objects 309
complete list 309

multistep widget 187, 188

data nodes 58
date input widget 91
delete link widget 94
domain picker widget 95

N
name attribute 58

P
F
file widget 98
finder control widget 144
finder query widget 100
finder result widget 115
form widget 146
framework link widget 147
functionality widget 149

G
generic text widget 150

H
hidden field widget 153
HTTP requestinitializing widgets from 60

page text widget 190


page title widget 191
page widget 195
parameters widget 205
pickersdomain picker widget 95
pickerslist of values (LOV) picker widget 184
pickersSaba picker widget 210
presence widget 206
prompt for save widget 208

S
Saba picker widget 210
save link widget 212
script widget 213
security 15
system architecture 14

| Index | 434

T
table widget 217
text area widget 228
time input widget 230
tree widget 232

V
validator widget 257

W
WDK pages 62
widget library 62
wdktagsattachTo 59
widgets 51, 62
defined 51
list of available widgets 62
widgetsattachments 66
widgetsattachTo subnode 59
widgetsbreadCrumb 68
widgetschart 70
widgetschartToolbar 77
widgetscomponentPicker 81
widgetscomponentWidget 84
widgetscustomFields 89
widgetsdata subnode 58
widgetsdateInput 91
widgetsdeleteLink 94
widgetsdomainPicker 95

widgetsfile 98
widgetsfinderControl 144
widgetsfinderQuery 100
widgetsfinderResult 115
widgetsform 146
widgetsframeworkLink 147
widgetsfunctionality 149
widgetsgenericText 150
widgetshiddenField 153
widgetsid subnode 58
widgetsimpresence 154, 161
widgetsinitializeFromRequest subnode 60
widgetsinput 155
widgetslink 162
widgetslist 178
widgetslovPicker 184
widgetsmultistep 187, 188
widgetspageText 190
widgetspageTitle 191
widgetspageWidget 195
widgetsparameters 205
widgetspresence 206
widgetsprompForSave 208
widgetssabaPicker 210
widgetssaveLink 212
widgetsscript 213
widgetstable 217
widgetstextArea 228
widgetstimeInput 230
widgetstree 232
widgetsvalidatorWidget 257
widgetsviewOnly subnode 61

You might also like