GNUe Forms: A Developer's Introduction

A Guide to Programming with GNUe Forms





























Version 0.5.1





Written by Jason Cater, your tour guide


































































Copyright © 2000-2003 Free Software Foundation.

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled GNU Free Documentation License.

Table of Contents

Introduction 4

Structuring the Database 4

Designing the Form 4

Planning for Security 4

Basic Concepts 5

Datasources 5

Blocks and Fields 5

Pages and Visual Elements 5

Triggers 5

Designing for Multiple Interfaces 5

Designing for Multiple Databases 5

Creating your First Form 6

Preliminary Steps 6

Creating the Empty Form 7

Creating the Datasources 7

Creating the Logic Structure 8

Creating the Layout 9

Running the Form 11

Where To Go Next 11

Understanding Datasources 12

Table-Bound Datasources 12

Static Datasources 12

Defining Conditions 13

Linking Datasources via Master/Detail 13

Advanced Relationships 13

Understanding Events and Triggers 14

Form's Event Model 14

Named Triggers verses Embedded Triggers 14

Form-level Triggers 14

Block-level Triggers 15

Field-level Triggers 18

Page-level Triggers 19

Entry-level Triggers 19

Button-Level Triggers 19

Working with Fields 21

Typecasting Fields 21

Default Values 21

Formatting Fields with Masks 21

Dropdown Fields 24

Check boxes 24

A Brief Introduction to Python 25

The Basics 25

Variables and Expressions 25

Control Structures 25

Tuples, Lists, and Dictionaries... oh, my! 26

Exploring Trigger Namespaces 27

Introduction 27

Object Heirarchy 28

Fields and Entries 28

Blocks 28

Datasources 28

Form 29

Creating and Using Libraries 30

Overview 30

Integration with GNUe Tools 31

Running Reports from Forms 31

Running Forms from Navigator 31

Advanced Topics 32

Runtime Parameters 32

External Python Modules 33

Designing for Multiple Interfaces 33

Trigger Recipes 35

Timestamping a Record prior to a Commit 35

Stamping a Record with User's Login prior to a Commit 35

Auto-Populating an Entry from a Sequence 36

Appendix A: Trigger Hierarchy 37

Appendix B: Form Elements 38

Form Tags 38

Connection Tags 38

Datasource Tags 39

Dialog Tags 43

Layout Tags 43

Logic Tags 46

Menu Tags 48

Options Tags 49

Parameter Tags 50

Trigger Tags 50

Import Tags 51

Appendix C: Form Objects 56

Form 56

Datasource 59

Block 60

Entry 62

Appendix D: Data Objects 65

Result Set 65

Record Set 65

Appendix E: Sample Librarian Schema 66

Appendix F: Glossary 67

Appendix G: GNU Free Documentation License 68



Introduction

This section briefly introduces the process of designing an application using GNUe Forms. blah, blah, blah...

Before designing an application for Forms, the developer should be somewhat familiar with a few key concepts:

Structuring the Database

TODO

Designing the Form

TODO

Planning for Security

TODO

Basic Concepts

TODO

Datasources

Since GNUe Forms was designed from the ground up to manipulate database data, it must have some way to tie the graphical elements to database tables. This is where a datasource comes into play.

A datasource provides a link to a database table or some similar data store.

[TODO: Expand]

Datasources are portable from one GNUe tool to another. A datasource that is usable in forms should also be usable in a report.

Blocks and Fields

A datasource provides a link to a table, but Forms needs more information than a simple reference to meaningfully interface with an end user. A block is the first step to making datasources suitable for such user interaction. At its most basic level, a block contains instructions on how Forms should interact with a datasource.

Any datasources that are to interact with a user must have a single corresponding block. Datasources that are only used internally and not for displaying data will not normally have an associated block, although the developer may choose to do so.

A field is ....

Pages and Visual Elements

TODO

Layout Management

TODO

Triggers

TODO

Designing for Multiple Interfaces

TODO

Designing for Multiple Databases

TODO

Creating your First Form

In this chapter, we will create our first full-featured form -- a postal code lookup table. [TODO: Expand]

[TODO: Replace the postal code example with librarian]

We are going to create a form that looks something like:

01234567890123456789012345678901234567890
|---------+---------+---------+---------|

Zip Code: [___]

City Name: [_______________________]

State: [__]



The ruler is simply for our layout reference. We will come back to it later.

[TODO: expand]

Preliminary Steps

[TODO: Intro]

The following two steps may need to be performed by your database or systems administrator.

First, we need to create a test table. Using your database of choice, create a table named zipcodes, with three appropriately sized fields, zipcode, city, and state. In PostgreSQL, SAP-DB, and others, the following statement will work:


create table zipcodes (
zipcode varchar(5),
city varchar(30),
state varchar(2) );


For other databases, create a similarly structured table.

Next, if you have not done so yet, setup your connections.conf file to point to your database. Our examples will use a connection called tutorial. An example entry:


[tutorial]
comment = Tutorial Database
provider = psycopg
host = localhost
dbname = mydb


Of course, your entry will probably look different. This example is using the PostgreSQL psycopg driver, connecting to a database named mydb running on the local machine. See the Forms Installation Guide for information on the location and syntax of this file.

If you already have a connection for the database you will be using, simply add an alias = tutorial line to the appropriate section. Example:


[dev]
comment = Foobar Development
provider = psycopg
host = dbserver
dbname = mydb
alias = tutorial




Creating the Empty Form

By default, GNUe Designer will start up with an empty form. You may also create a new form by selecting File | New | Form.

[TODO: change the title of the form]

Creating the Datasource

Our sample form will use a single table - zipcodes. To access this table in Forms, we need to associate it with a datasource. We will call this datasource MyDS.

From the menu, select Edit | Insert | Datasource... to run the datasource wizard. The first step of the datasource wizard asks for your connection. Select tutorial. This is the connection we setup in the first part of this section. Click Next... You may be asked to log in. Provide a valid database username and password.

It will need three attributes: name, connection, and table.

Creating the Logic Structure

[TODO: Explain the logic section and tag]

Blocks are the display equivalent of datasources. Since we are working with a single datasource, we will have a corresponding single block. Since this is tied to the zipcodes table, we will call this block ZipBlock.

Block-specific attributes:

Field-specific attributes:

.....

Creating the Layout

Forms contains its layout logic in units called pages. Only a single page is normally seen any given time by the end user. Our simple form will need one page. We will call it MyPage.

Given the layout grid we created earlier, we see that our form will be 40 characters wide and 7 lines high. For simplicity's sake, we are using a simple character based layout, identified as GNUe:Layout:Char. In the future, forms will support other layout styles.

Again, looking back at our earlier layout grid, we have three labels and three entries. Each label starts in column two and each entry starts in column 13. Each pair of label/entry skips a row, with the first pair being on row 1.

This gives us enough information to create our display layout.

Via GNUe Designer:

An empty form created by GNUe Designer automatically has a single, empty page. If we were to add more pages, this could be accomplished by selecting Edit | Insert | Page. However, this example uses a single page, so we do not need to do anything for this step.



[TODO]

Running the Form

TODO

[TODO: Insert pic of Win32 form]

[TODO: Insert pic of HTML form]

Where To Go Next

[TODO: Well, home *is* where the heart's at]

Understanding Datasources

A Datasource links data to our form. Usually, a datasource points to a table if using a relational database, or a data object if using an object database. A form can have several datasources if pulling data from multiple locations, or no datasources at all if the form does not reference outside data.

If a form does not have a datasource, a virtual datasource is created. The commit, rollback, and query functions do not serve a purpose against virtual datasources. This is particularly useful for action forms that simply cause actions to occur, but do not directly manipulate data.

Datasources can be linked to each other in a master/detail fashion via a foreign key. In essence, each time the master datasource changes, the detail datasource is automatically requeried to bring up records related to the master. See the section on master/detail relationships for more information.

Table-Bound Datasources

The most common datasource is one that is bound to an individual table or view.

Static Datasources

TODO


<datasource name="AvailDS" type="static">
<staticset fields="id,descr">
<staticsetrow>
<staticsetfield name="id" value="A"/>
<staticsetfield name="descr"
value="Available"/>
</staticsetrow>
<staticsetrow>
<staticsetfield name="id" value="N"/>
<staticsetfield name="descr"
value="Not Available"/>
</staticsetrow>
<staticsetrow>
<staticsetfield name="id" value="B"/>
<staticsetfield name="descr"
value="Backordered"/>
</staticsetrow>
</staticset>
</datasource>


TODO

Defining Conditions

Form's datasources support conditions. Conditions place restrictions on the records returned by a datasource. For those familiar with SQL, a condition translates directly into a WHERE clause.


<datasource connection="test" table="reps">
<condition>
<or>
<eq>
<cfield name="active"/>
<cconst value="Y"/>
</eq>
<gt>
<cfield name="sales_ytd"/>
<cconst value="0"/>
</gt>
</or>
</condition>
</datasource>


In this example, we are basically only allowing records from the reps table where the representative is either active (active = 'Y') or had sales this year.

Linking Datasources via Master/Detail

Quite often, you will want a second datasource's behavior to be tied to a primary datasource. If a new record is queried in the first datasource, all corresponding records in the second datasource should automatically appear. Likewise, creating a new record in the first datasource should clear out the second datasource and any subsequent new records in this second datasource will be automatically associated with the new primary record.

In GNUe, we call this relationship a Master/Detail relationship. It closely mirrors the concept of primary/foreign keys in relational databases.

Master/detail relationships have the following properties:

Defining Master/Detail Datasources

There are no special attributes for a master datasource to indicate its role as master.

The detail datasource, on the other hand, has three special attributes that must be provided: master, masterlink, and detaillink.

Master/Detail Considerations

TODO

Advanced Relationships

TODO

Master/Detail/Detail

TODO

Reverse Master/Detail

TODO

Understanding Events and Triggers

TODO

Form's Event Model

TODO

Named Triggers verses Embedded Triggers

TODO

Form-level Triggers

A Form-level trigger is defined as an object that is activated at the form-level and is defined as a child of the form object.

The following form level triggers are defined.

On-Startup

The On-Startup trigger is executed once during the lifetime of a Form's instance. This happens after all the objects have been initialized and initially populated.

Suggested uses for On-Startup:

On-Activate

The On-Activate trigger is executed each time a form or dialog is activated. For a normal form, the difference between On-Activate and On-Startup is very nominal. However, for dialog-style forms, the difference is more pronounced. On-Startup will only be executed once in the form's life, whereas On-Activate is called every time the dialog is instantiated and displayed.

For non-dialog forms, we recommend you use On-Startup.

Suggested used for On-Activate:

On-Exit

The On-Exit trigger is executed when either the user or a trigger requests that a form closes.

Suggested uses for On-Exit:

Pre-Commit

The Pre-Commit trigger is executed before a form-level commit occurs.

Suggested uses for Pre-Commit:

Post-Commit

The Post-Commit trigger is executed after a form-level commit occurs.

Suggested uses for Post-Commit:

Block-level Triggers

All block-level triggers are executed on a per-record basis. That is, a trigger would get executed once for every applicable record, not just once for the entire block.

Pre-Query

The Pre-Query trigger is executed before a query against the database occurs. This trigger is unique from all other triggers in that it is called while the form is in query mode -- ie., the same mode as selecting Data | Enter Query from the menu. This means that any field changes made by this trigger don't actually modify a record, but instead are used as query conditionals.

Suggested uses for a block-level Pre-Query trigger:

Post-Query

The Post-Query trigger is executed after a query occurs on a record. A block-level Post-Query is executed once for each record that was loaded from a database. Post-Query is the counter-part to On-NewRecord in that one or the other should be executed for every displayed record.

NOTE: Post-Query is only fired as a record is loaded from the database. This implies that, with GNUe's caching system, if only 10 records are displayed on screen at a time out of a table of 100 records, then only the first 10 or so records will have Post-Query fired. It is guaranteed, however, that by the time a the user can see a loaded record or before another trigger can be fired against a loaded record, Post-Query has already been called. It is possible to get around this by, at some point [TODO: where?], calling block.lastRecord() and (optionally, if needed) block.firstRecord().

Suggested uses for a block-level Post-Query trigger:

Pre-Change

The Pre-Change trigger is executed before a record is initially modified -- i.e., when the first field of a record is set to a new value. At the time the Pre-Change record is called, the modified field will still contain the old value.

Suggested uses for a block-level Pre-Change trigger:

Pre-Insert

The Pre-Insert trigger is executed before a commit occurs on a record that is pending an insertion. A block-level Pre-Insert is executed once for each inserted record and is fired prior to the Pre-Commit trigger.

Suggested uses for a block-level Pre-Insert trigger:

Pre-Update

The Pre-Update trigger is executed before a commit occurs on a record that is pending an update. A new or deleted record is not considered "updated" for the purpose of this trigger. A block-level Pre-Commit is executed once for each changed record and is fired prior to the Pre-Commit trigger.

Suggested uses for a block-level Pre-Update trigger:

Pre-Delete

The Pre-Delete trigger is executed before a commit occurs on a record that is pending a deletion. A block-level Pre-Commit is executed once for each record that has pending deletion. A block-level Pre-Delete fires prior to a Pre-Commit trigger.

Suggested uses for a block-level Pre-Delete trigger:

Pre-Commit

The Pre-Commit trigger is executed before a commit occurs on a record. A block-level Pre-Commit is executed once for each record that has pending changes, including new and deleted records. A block-level Pre-Commit fires prior to a Field's Pre-Commit trigger, but after the Pre-Insert, Pre-Update, and Pre-Delete triggers.

Suggested uses for a block-level Pre-Commit trigger:

Post-Commit

The Post-Commit trigger is executed after a commit occurs on a record. A block-level Post-Commit is executed once for each record that had pending changes, including new and deleted records. A block-level Post-Commit fires after a Field's Post-Commit trigger.

If a Post-Commit trigger modifies the values in a record, then the record will be, once again, pending changes. Typically a Post-Commit trigger would not modify any values and you could create an unsavable form.

Suggested uses for a block-level Post-Commit trigger:

On-NewRecord

The On-NewRecord trigger is executed when a record is initially created. This trigger is executed once for each new record at the time of creation in the form.

Suggested uses for a block-level On-NewRecord trigger:

Pre-FocusIn

The Pre-FocusIn trigger is executed as a new record is focused in a block. It is recommended that unless you have a specified understanding of the intention of forms, use Post-FocusIn instead of Pre-FocusIn as the latter trigger's behavior may change at some point to better reflect record focus.

Suggested uses for a block-level Pre-FocusIn trigger:

Post-FocusIn

The Post-FocusIn trigger is executed as a new record is focused in a block. This may be triggered by a user navigating to a different record or by creating a new record. The actual record change has occurred when this trigger is fired.

Suggested uses for a block-level Post-FocusIn trigger:

Pre-FocusOut

The Pre-FocusOut trigger is executed as a different record is about to be focused in a block. This may be triggered by a user navigating to a different record or by creating a new record. The actual record change has not occurred when this trigger is fired.

Suggested uses for a block-level Pre-FocusOut trigger:

Post-FocusOut

The Post-FocusOut trigger is executed as a new record is focused in a block. It is recommended that unless you have a specified understanding of the internals of forms, use Pre-FocusOut instead of Post-FocusOut as the latter trigger's behavior may change at some point to better reflect record focus.

Suggested uses for a block-level Post-FocusOut trigger:

Field-level Triggers

TODO

Pre-FocusIn

TODO

Post-FocusIn

TODO

Pre-FocusOut

TODOglimpse

Post-FocusOut

TODO

Post-Query

TODO

Pre-Modify

TODO

Pre-Insert

TODO

Pre-Update

TODO

Pre-Delete

TODO

Pre-Commit

TODO

Post-Commit

TODO

Pre-Change

TODO

Post-Change

TODO

Page-level Triggers

TODO

Pre-FocusIn

TODO

Post-FocusIn

TODO

Pre-FocusOut

TODO

Post-FocusOut

TODO

Entry-level Triggers

TODO

Pre-FocusIn

TODO

Post-FocusIn

TODO

Pre-FocusOut

TODO

Post-FocusOut

TODO

Button-Level Triggers

Buttons have a special relationship with triggers.

On-Action

This trigger is run when the user activates (e.g., clicks) a button.

Suggested uses for an On-Action trigger:

Pre-FocusIn

TODO

Post-FocusIn

TODO

Pre-FocusOut

TODO

Post-FocusOut

TODO

Post-Change

TODO



Working with Fields

TODO

Typecasting Fields

TODO

Default Values

TODO

Formatting Fields with Masks

[ NOTE: Format Masks are not yet completely functional! This section reflects the intended support ]

Forms supports two types of format masks: display masks and input masks. A display mask defines how the field data will be formatted for display. An input mask defines how the user will edit a field's value. Input mask elements are a subset of display mask elements -- in other words, all input masks can also be used as display masks, but not all display masks can be used as input masks.

Note: if first character of a format is '&', then rest of date defines a preset format (settable by developer? in gnue.conf or geas?).

e.g., in gnue.conf:


FormatDate_longdate = "A, b d, Y"


Then, in the client, the format string could be: &longdate

This allows reuse of common format masks throughout the application.

Formatting Numeric Fields

TODO

Formatting Date/Time Fields



Element

Input?

Description

\

Yes

Next character is a literal

a

Yes

Abbreviated weekday name (Sun..Sat)

A




Full weekday name (Sunday..Saturday)

b

Yes

Abbreviated month name (Jan..Dec)

B




Full month name (January..December)

c




Century (20,21)

d

Yes

Day of month, left padded with zeros (01..31)

D




Day of month, non-padded (1..31)

h

Yes

Hour (24-hour format), left padded with zeros (00..23)

H




Hour (24-hour format), non-padded (0..23)

g

Yes

Hour (12-hour format), left padded with zeros (01..12)

G




Hour (12-hour format), non-padded (1..12)

j

Yes

Day of year, left padded with zeros (001..366)

J




Day of year, non-padded (1..366)

m

Yes

Month, left padded with zeros (01..12)

M




Month, non-padded (1..12)

i

Yes

Minute, left padded with zeros (01..59)

I




Minute, non-padded (1..59)

p

Yes

am/pm designation (lowercase)

P




AM/PM designation (uppercase)

s

Yes

Seconds, left padded with zeros (00..59)

S




Seconds, non-padded (0..59)

u

Yes

Week number of year with Sunday as first day of week, left padded with zeros (01..52)

U




Week number of year with Sunday as first day of week, non-padded (1..52)

v

Yes

Week number of year with Monday as first day of week, left padded with zeros (01..52)

V




Week number of year with Monday as first day of week, non-padded (1..52)

w

Yes

Day of week with Sunday as first day of week (0=Sunday) (0..6)

W

Yes

Day of week with Monday as first day of week (0=Monday) (0..6)

y

Yes

Year (1900..2100)

Y

Yes

Year, using 2-digit notation (00..99) When used as an input mask, forms tries to reasonably guess the century. (TODO: Elaborate)

Predefined literals: "/-.:, "

Examples: 01/01/2001: "m/d/y" Friday, June 1, 2001: "A, b d, Y"

Formatting Text Fields

TODO

Dropdown Fields

TODO

Check boxes

TODO

A Brief Introduction to Python

While GNUe Forms will eventually support a plethora of scripting languages, the default, and best-supported, language will always be Python. Python is a ...........

If you do not know Python, don't worry! Python is one of the simplest languages to pick up. .............



Once multi-language support is added, the developer will be able to write triggers in Python, Perl, Ruby, Scheme, or possibly even Basic. .............

While Python is easy to learn, this section assumes that you know at least one programming language. It is beyond the scope of this guide to cover basic programming concepts. There are several excellent Python tutorials for those beginning programming available on the Web. Go to http://www.python.org/doc/ for a listing of available tutorials. They have docs for every stage of python programming, from new-to-programming to seasoned veteran.

The Basics

The first thing most people notice about Python is its reliance on whitespace for grouping.

Variables and Expressions


x = 1


Control Structures


if x == 1:
print "Yip"



for f in (1,2,3):
print f



for f in range(4):
print f



n = 1
while n < 10:
n += 1


Tuples, Lists, and Dictionaries... oh, my!

TODO

Exploring Trigger Namespaces

Introduction

TODO

Global Names

GNUe Forms supports the Python global construct, which can be used by the developer to define global variables and methods, or import modules globally. For example, assume the following code chunk is a form's On-Startup trigger:


##
## On-Startup [Form]
##

# We want to give our other triggers
# access to these three objects.
global math, myfunc, DEBUG

# We will use the math module a lot
# in our other triggers
import math

# A handy function
def myfunc(n1,n2):
return n1+n2

# Are we in DEBUG mode?
# Enquiring triggers want to know...
DEBUG = 1

# This is an example of a non-global name.
# Only our On-Startup trigger sees this.
test = 2


Because Forms executes On-Startup before any other triggers, all other triggers within this form can now see math, myfunc, and DEBUG.

For example, an On-Change trigger could now do:


##
## On-Change (MyEntry)
##
if DEBUG:
print "Starting value: %s" % self.get()

computed = myfunc(self.get(), 12)

AnotherEntry.set(math.floor(computed))


Note that if another trigger wanted to globally change the values of math, myfunc, or DEBUG, they would also have to use the global construct. The following section of code would only change DEBUG for this single execution of On-Change:


##
## On-Change (MyEntry)
##
DEBUG = 0

# ... other code ...


The other code in this example would see DEBUG as being 0, but once the trigger was completed, DEBUG would return to being 1 for all future triggers. Now, suppose the trigger had instead looked like:


##
## On-Change (MyEntry)
##
global DEBUG
DEBUG = 0

# ... other code ...


Now, this trigger and all future triggers will see DEBUG as 0.

Object Heirarchy

TODO

Fields and Entries

TODO

Blocks

TODO

Datasources

Datasource objects

The datasource objects, in addition to providing sensible methods for data access and manipulation, also act as python container objects whenever possible. For example, a recordset can behave as a python dictionary, and resultsets behave as tuples. Any container-related behavior will be discussed after the supported methods are detailed.

Datasource

ResultSet

RecordSet

Examples

TODO


resultset = MyDataSource.createResultSet({'id':1})
recordset = resultset.nextRecord()
if recordset:
DescrField.set(recordset['description'])


TODO


resultset = MyDataSource.createResultSet()
recordset = resultset.createRecord()
recordset['id'] = 1
recordset['description'] = 'Test Record'
recordset['amount'] = 10.00


TODO


resultset = MyDataSource.createResultSet({'id':1})
recordset = resultset.nextRecord()

# Delete the last record
recordset.deleteRecord()


TODO


# Query all records where amount is 5.00
resultset = MyDataSource.createResultSet({'amount':5})
for recordset in resultset:
recordset['queue'] = 'Y'


TODO

Form





Creating and Using Libraries

Overview

TODO

Integration with GNUe Tools

Running Reports from Forms

TODO

Running Forms from Navigator

TODO

Advanced Topics

This section describes advanced forms concepts. [TODO: expand]

Runtime Parameters

Forms supports runtime parameters that can be passed to a form instance at startup. Parameters are mainly useful for specifying conditions in datasources, but can also be accessed via triggers. This allows the perceived behavior of a form to be altered only by passing a parameter.

A good example is a form designed to service two divisions of a company. While you could offer an opening dialog that asks the user which division he wants to work on, an alternative is to modify his startup script to tell the form which division he works with. This especially works well when the worker only belongs in one division and will never need access to any others.

Note that in the above example, though, parameters are not a good substitute for access security. This example would strictly be for convenience, not security.

Parameters must be defined, with a default value, using the <parameter> tag:


<form>
<parameter name="division" default="101"/>
...
</form>


Once defined, parameters can be passed to forms in one of two ways. The first is via the command line. Parameters can be passed in the format parameter=value on the command line appearing after the name of the form. For example:


gnue-forms myform.gfd division=101


Alternately, if the form is being called from another form, the trigger would look like:


## Run "myform.gfd"
form.runForm( 'myform.gfd', { 'division' : 101 } )


That is, the parameters would be passed to runForm as a Python dictionary. Once passed to the form, parameters can be used in one of two ways: via trigger code or as a parameter to a datasource condition.

First, triggers can access parameters using the form.getParameter() method. This method takes one argument, the case-insensitive parameter name. It returns the requsested parameter, or the default value if no parameter was passed on startup.


## Get the "company" parameter
division = form.getParameter('division')


Conditions are also a good place for parameters. Take the following fragment:


<datasource name="dtsExsample" table="sales"
connection="sales"/>
<condition>
<eq>
<cfield name="division"/>
<cparam name="division"/>
</eq>
</condition>
</datasource>


With this in place, whenever the table sales is queried, the only records returned are the ones where the field division matches the parameter division. Note that if this datasource will also be used for inserting new rows, a Pre-Insert trigger is needed to set the division field:


##
## Pre-Insert [SalesBlock]
DivisionEntry.set(form.getParameter('division'))


External Python Modules

Python triggers have full access to your installed Python modules. For example, if your project needs the twofish cryptographic module, you can install it normally and do an import twofish in your triggers.

Alternately, GNUe's gnue.conf file supports an ImportPath directive. You can have this point to a directory containing your custom python modules.

Designing for Multiple Interfaces

A form definition, when designed within reasonable guidelines, can be run on a plethora of system architectures and a wide variety of user interfaces. By using the approach taken in this guide, most of your forms will, by default, run on a graphical workstation (X11, Windows, Mac), in a text-based session (telnet or ssh), or via a web browser (HTML). This section highlights a few key compatibility issues.

This list, while not exhaustive, should give you a good idea of common portability pitfalls. As with all things in GNUe, you will always have a choice on how to implement your application. GNUe is not about forcing rules on developers, but about providing viable options. There will be instances where the following suggestions simply are not feasible or practical. In any event, these are simply suggestions on getting the most out of Forms.

Trigger Recipes

Over the coarse of writing a complex application, you will encounter a few situations where you will need a trigger to perform a common task. This section lists several

Timestamping a Record prior to a Commit

To automatically fill an entry with a timestamp retrieved from the database, you can use the datasource extension getTimestamp(). create a Pre-Commit trigger on that block. For example,


##
## Pre-Commit [MyBlock]
##

self.MyTimeField.set(MyDS.extensions.getTimestamp())


This example assumes your entry is named MyTimeField and your datasource is called MyDS.

As noted elsewhere in this guide, Pre-Commit is run prior to saving changes to the database regardless of whether the record in question is being inserted, updated, or deleted. If you want to timestamp only new records, you can use the same code listed above, only inside a Pre-Insert trigger. Similarly, if you only want to timestamp modifications, you can use a Pre-Update trigger.

At this point, you may be asking why did we go through MyDS to get to a database timestamp. After all, MyDS corresponds to a table, not to a database. [TODO: Provide an explanation]

By using a timestamp retrieved from the database server, you do not have to worry about differences in the client machines' times. If you would prefer to have the client's time, you can use python's time module.

Stamping a Record with User's Login prior to a Commit

To automatically fill an entry with a the user's login name, you can use form.getAuthenticatedUser() and creating a Pre-Insert trigger on that block. For example,


##
## Pre-Insert [MyBlock]
##

self.CreatedBy.set(form.getAuthenticatedUser())


This example assumes your entry is named CreatedBy. As noted elsewhere in this guide, Pre-Insert is run prior to saving a new record to the database.

This method is commonly called alongside the timestamping recipe above. Together, a Pre-Insert trigger to stamp a new record might look like:


##
## Pre-Insert [MyBlock]
##

self.CreatedBy.set(form.getAuthenticatedUser())
self.CreatedOn.set(MyDS.extensions.getTimestamp())


See the recipe for timestamping for more information on usage of getTimestamp().

Auto-Populating an Entry from a Sequence

To automatically fill an entry with a value from a sequence, you can create a Pre-Insert trigger on that entry. For example,


##
## Pre-Insert [MyEntry]
##

self.set(MyDS.extensions.getSequence(“MySequence”))


This example assumes your entry is named MyEntry, your datasource is called MyDS, and the sequence name as stored in the database is MySequence. Note that MyEntry and MyDS are both names originating in your form, whereas MySequence is a name originating in your database.

At this point, you may be asking why did we go through MyDS to get to a database sequence. After all, MyDS corresponds to a table, not to a database. [TODO: Provide an explanation]

Appendix A: Trigger Hierarchy

Forms supports



Appendix B: Form Elements

[TODO: Introduction]

Form Tags

form

No description provided

Attributes

Attribute

Values

Default

Description

name

text


A unique ID for the form.

readonly

Y, N

N

If set to Y, then no modifications to data by the end user will be allowed. The form will become a query-only form.

style

dialog


No description provided

title

text

Untitled Form

The title of the form.

Child Nodes

connection, datasource, dialog, import-datasource, import-dialog, import-layout, import-logic, import-trigger, layout, logic, menu, options, parameter, trigger

Connection Tags

connection

No description provided

Attributes

Attribute

Values

Default

Description

name

text


No description provided

provider

text


No description provided

comment

text


No description provided

dbname

text


No description provided

host

text


No description provided

service

text


No description provided

Datasource Tags

datasource

No description provided

Attributes

Attribute

Values

Default

Description

name

text


No description provided

cache

number

5

No description provided

connection

text


No description provided

detaillink

text


No description provided

distinct

Y, N

N

No description provided

explicitfields

text


No description provided

master

text


No description provided

masterlink

text


No description provided

order_by

text


No description provided

prequery

Y, N

N

No description provided

primarykey

text


No description provided

table

text


No description provided

type

text

object

No description provided

Child Nodes

condition, staticset

add

No description provided

Child Nodes

add, cconst, cfield, cparam, div, mul, sub

and

No description provided

Child Nodes

and, between, conditions, eq, ge, gt, le, like, lt, ne, negate, not, notbetween, notlike, notnull, null, or

between

No description provided

Child Nodes

add, cconst, cfield, cparam, div, mul, sub

cconst

No description provided

Attributes

Attribute

Values

Default

Description

value

text


No description provided

cfield

No description provided

Attributes

Attribute

Values

Default

Description

name

text


No description provided

condition

No description provided

Child Nodes

and, between, eq, ge, gt, le, like, lt, ne, negate, not, notbetween, notlike, notnull, null, or

cparam

No description provided

Attributes

Attribute

Values

Default

Description

name

text


No description provided

div

No description provided

Child Nodes

add, cconst, cfield, cparam, div, mul, sub

eq

No description provided

Child Nodes

add, cconst, cfield, cparam, div, mul, sub

ge

No description provided

Child Nodes

add, cconst, cfield, cparam, div, mul, sub

gt

No description provided

Child Nodes

add, cconst, cfield, cparam, div, mul, sub

le

No description provided

Child Nodes

add, cconst, cfield, cparam, div, mul, sub

like

No description provided

Child Nodes

add, cconst, cfield, cparam, div, mul, sub

lt

No description provided

Child Nodes

add, cconst, cfield, cparam, div, mul, sub

mul

No description provided

Child Nodes

add, cconst, cfield, cparam, div, mul, sub

ne

No description provided

Child Nodes

add, cconst, cfield, cparam, div, mul, sub

negate

No description provided

Child Nodes

and, between, conditions, eq, ge, gt, le, like, lt, ne, negate, not, notbetween, notlike, or

not

No description provided

Child Nodes

and, between, conditions, eq, ge, gt, le, like, lt, ne, negate, not, notbetween, notlike, notnull, null, or

notbetween

No description provided

Child Nodes

add, cconst, cfield, cparam, div, mul, sub

notlike

No description provided

Child Nodes

add, cconst, cfield, cparam, div, mul, sub

notnull

No description provided

null

No description provided

or

No description provided

Child Nodes

and, between, conditions, eq, ge, gt, le, like, lt, ne, negate, not, notbetween, notlike, notnull, null, or

staticset

No description provided

Attributes

Attribute

Values

Default

Description

fields

text


No description provided

Child Nodes

staticsetrow

staticsetfield

No description provided

Attributes

Attribute

Values

Default

Description

name

text


No description provided

value

text


No description provided

staticsetrow

No description provided

Child Nodes

staticsetfield

sub

No description provided

Child Nodes

add, cconst, cfield, cparam, div, mul, sub

Dialog Tags

dialog

No description provided

Attributes

Attribute

Values

Default

Description

name

text


A unique ID for the form.

readonly

Y, N

N