Servoy Tips and Tricks: From Thesupportgroup
Servoy Tips and Tricks: From Thesupportgroup
We have collected a variety of Servoy tips, tricks and techniques that we learned
from building Servoy applications for our clients. We want to share these with
other Servoy developers to assist you in creating better and more robust
applications. We welcome any comments and feedback you may have at
[email protected].
Abou t The S up po r t G ro up
To learn more about our Servoy design, development and training services, visit
our website at www.servoysupportgroup.com.
How to avoid scrollbars that extend through the header and footer on list/table
views
Changing the display of one row in a list view without affecting all rows
Anchoring basics
controller.loadRecords(dataset) versus
controller.loadRecords(query)
Modularizing
Real-time logging
Leaving breadcrumbs
There are many times that a single record needs to be loaded into a form. This
may be necessary for exporting, printing, duplication or security reasons. Here’s
a global method that allows one record to be loaded, given the primary key of the
record to load and the form name. This technique uses a SQL query in
conjunction with controller.loadRecords(), which is more reliable and
faster than controller.find():
forms[theForm].controller.loadRecords(theQuery);
globals.loadSingleRecord("frm_company", 8);
One of Servoy’s great interface features is the ability to display HTML formatting
in fields set to the display type “HTML area.” The HTML area allows the
developer to build a data-driven interface that is created at run time, rather than
dragging and dropping objects onto a layout. The HTML area can even have
clickable buttons and hyperlinks to call methods. For example, here’s the some
HTML that triggers the load_selected_contact method and passes in a
contact id:
It's possible using this technique to create an entirely dynamic form, based on
database values, and presented using an HTML area that users can interact
with. Here’s a calendar, where the headings on each appointment are hyperlinks
that pass the appointment ID to a method that shows the appointment details:
To some fastidious developers, the fact that Servoy’s scrollbar extends through
the header and footer on list and table views is visually unappealing and slightly
misleading in terms of functionality. To get around this issue, you will need to
create an extra form that contains the list or table view without headers and
footers. Then, on your main list view, set up the header and footer, set the
display type to record view and expand the body section to the size of the
other “detail” view screens in your application. Then place a tabpanel in the body
section that looks over to your “real” list view screen and set the anchors to all
four sides. Now you have a list view with a better-looking and functionally-
accurate scrollbar.
Servoy’s flexibility at runtime is one of its defining features: forms, HTML and
SQL can all be modified while the application is running to produce results that
are reflective of users’ preferences, actions and the data in the database. Even
methods can be left somewhat open-ended by using Servoy’s bracket notation
when referring to forms and elements. The method below can change the
foreground (font) color of any button that triggers it, by determining which form
and element caused the event and then using bracket notation to modify that
element:
forms[theForm].elements[theElement].fgcolor = '#ff0000';
Have you ever found yourself running the same minor routines in a group of
calculations and wished that you could factor out the common parts as you can in
the method editor? Although it is undocumented, this is possible in Servoy’s
calculation editor by triggering a global method.
This technique should only be used for very simple and non-processor-intensive
routines, such as converting fractions to decimals or formatting a piece of HTML.
Global methods that are to be called from calculations should not affect the
interface directly, nor should any potentially time-intensive functions be used,
such as SQL queries or calls to remote servers.
The short answer is: table view, whenever possible. Servoy’s table view is much
more efficient at loading medium to large datasets than list view. Table view
takes data returned from a query and places it in a grid, whereas list view must
“paint” each row. For long lists or situations where bandwidth is limited, a change
from list view to table view and make the application feel twice as fast. Table
view is also able to take advantage of almost all of formatting niceties available in
list view: alternating colored rows, highlighting the selected record and HTML
display.
List view:
Table view:
Servoy’s one click logging is one of its most powerful and easy to use features.
Once you enable tracking in the security preferences and select which tables to
track, you can see a log of all changes to a given record, which can be handy for
both auditing and reverting data. The one difficulty comes in how Servoy stores
the primary key data of the record being modified, as it must take into account
tables with more than one primary key. Here’s a quick global method that will
allow a user or administrator to quickly pull up a list of all changes to the currently
viewed record. This method needs a table name and a record ID to run, though it
would also be possible to derive these two pieces of information and have zero
arguments:
forms.log_list_popup.controller.find();
forms.log_list_popup.table_name = theTable;
forms.log_list_popup.column_name = theField;
forms.log_list_popup.pk_data = '%.' + theRecordID + ';%';
forms.log_list_popup.controller.search(true, false);
forms.log_list_popup.controller.sort('event_time asc')
application.showFormInDialog(forms.log_list_popup, -1, -1, -1, -1,
'Edit history’, false, false, false);
Figuring out when to use a global method and when to use a form method is one
of the tricks of programming in Servoy that takes some practice. Typically, we
look for code that has the potential for reuse and could be applicable on many
different forms. Because almost all code meets the first criteria, it can be
tempting to start writing methods as globals, even if the opportunity to reuse the
code has not yet presented itself.
There's a reason for Servoy’s form-based method tree structure: it helps you
organize and keep track of the many methods in your solution. Global methods,
on the other hand, appear in one long list and are entirely dependent on good
naming conventions to make them findable. It is better to make methods at the
form level in order to take advantage of Servoy’s built in organization and only
elevate them to global status when it is clear that they are reusable.
One way that a developer can reduce the amount of bandwidth and resources
consumed by Servoy is to minimize the amount of data broadcasting that occurs
between clients. Broadcasting is a great feature of Servoy Server but it is often
triggered unnecessarily, causing extra data change messages to be transmitted.
The easiest was to prevent this from happening is to avoid long lists of records
that are of questionable usefulness to the user.
For example, imagine that you have solution that tracks jobs, and every morning
all users open the application and start by viewing the 200 most recent jobs.
Each person has now cached all of these records and as they are changed
throughout the day, data will be broadcasted to all of the clients.
Now imagine that each user typically needs to look at their own jobs and only
occasionally needs to browse the jobs of others. By loading only the current
user’s jobs into the job list at start up, you can reduce the number of records
cached on each client and thereby make a drastic reduction in the amount of
data broadcasting that will occur.
In a list view, it is common to show information from the table on which the form
is based, as well as information from other, related tables. Showing the related
data means that as each row is loaded, the related records must also be loaded
into Servoy’s cache and these related records are then subject to its data
broadcasting rules. For records that change often and that all users need to view
on a regular basis, it is a good idea to let the cache update as other users modify
records. For more static information, or records that only select users need to
see, it may make sense to avoid loading the related records in the first place.
This can be accomplished by populating a value list when the solution loads.
Imagine that you have an order record, and on that order record you have a
salesperson ID. You wish to show a list of orders and one of the columns that
you wish to show in this list is the salesperson’s name. While you could place a
related field from the salesperson table on your list view, this means that if
someone browses the order list, they will load dozens or perhaps hundreds of
related salesperson records into their cache.
There are often situations where it would be useful show different fields or visual
indicators on different rows in a list view. If you have ever tried to use Servoy’s
properties for manipulating elements in a list, such as setting the foreground
(font) color, or hiding or showing a field, you know that this change will affect
every row in the list, not just a single row as you might hope. The solution is to
use a combination of a calculation and HTML to change the display based on the
row and its data. There are three ways to use this technique.
The first is to change the display of only one field: perhaps we want to show an
amount due field in a red font when payment is late. We could do this by creating
a calculation field that produces HTML to color the field:
if(status == "Overdue")
{
return "<html><body><span color='#ff0000'>" + status +
"</span></body></html>";
}
else
{
return "<html><body><span color='#000000'>" + status +
"</span></body></html>";
}
The second approach is needed when several fields in a row must be altered, or
the entire background color must change. Here’s the HTML for changing the
background color based on database value. This field on the form would stretch
across the entire body section in list view, resulting in certain rows being
highlighted:
Servoy’s anchor property allows the developer to create layouts that stretch as
the user resizes the window. Not only does this allow for the wide range of
screen sizes available in most organizations, it also gives the users a strong
sense that your Servoy solution is “real”, rich desktop application.
Of the three types of form views, table view is the easiest to add anchors to. In
most situations, you can simply select all of the fields in the row and turn on
anchors for the right side (“top” and “left” are already on by default). Then just
make sure to turn horizontal scrolling off on the form and you’ll have a flexible,
expandable layout. You can even resize the different columns and those on
either side of it will also resize accordingly.
When using list view or record view, things are somewhat more complicated. On
a given axis, you can have only a single element stretch, while all of the others
need to be fixed to one or two of the sides. If everything is allowed to stretch as
the screen grows, you can quickly end up with an unusable mess of overlapping
elements. The detail view screenshot on the next page demonstrates one way
that his form could be set up to take advantage of anchors, where the most
important fields and elements are allowed to stretch, and all others are fixed and
set to anchor to the nearest side:
var theDataset =
databaseManager.getDataSetByQuery(controller.getServerName(), theQuery,
null, 10000);
controller.loadRecords(theDataset)
which uses “IN” to search for records that match the given list of primary keys. If
10000 rows are returned in the initial query, only 200-1000 are loaded; then we
have a problem. The other downside is that, if our upper limit for “IN” is 1000
records, Servoy’s standard procedure of loading only 200 records at a time (good
in terms of performance) is ignored and all 1000 of the records are loaded.
controller.loadRecords(theQuery);
Since version 3.1 this limitation has been removed and it is now possible to edit
modules directly from the main solution. With this power comes new
responsibility, because at the very least, the constant opening and closing of
solutions in previous versions forced the developer to be very clear about where
one module ended and another began. The temptation now that all of the editing
can be done from one place is to blend the solutions to such a great degree that
the point of modularizing (reusability) is lost.
In finding out how fix bugs in your applications, 90% of the battle is getting good
information about what exactly is happening as the program runs. There are
some great tools in the editor portion of Servoy Developer to aid in gathering this
information, from the debugger with its ability to step through methods to the
variable viewer that gives insight into the current values of method variables. The
web-based administration console is also useful in hunting down bugs and
performance issues with the “Server Log” and “Database Performance” sections.
Debugging right in the client is also an option, and this method will sometimes
allow you to click though the application and catch bugs that may have been
missed using the approaches mentioned above.
Once this change is made, save it and launch Client by double clicking on the
.jnlp file. You can then use a command line tool such “tail” (Mac/Linux) to view
the log as it is updated:
tail -f /Users/yourusername/.servoy-client-log.txt
There are several ways that you can print images stored in “blob” columns from a
Servoy interface: create a form to show the image and print it, convert the image
to a .pdf and send that to the printer or write a method to open the file in external
application and let the user print it from there. Here’s another interesting way to
accomplish this task using Servoy’s ability to execute actions at the command
line, using executeProgramInBackground to employ built in functionality of
Mac OS X or Windows XP.
First, we write out a temporary file from the media field. Then we call the
command line utility that will let us print. On the Mac platform we also have to tell
the command line about the page orientation we want to use.
if(utils.stringMiddle(application.getOSName(), 1, 7) == "Windows")
{
application.executeProgramInBackground('RunDll32.exe',
'shimgvw.dll,ImageView_PrintTo', '/pt', theTempFile,
theSelectedPrinter, '', '');
}
else // for the Mac
{
if(print_orientation == "Landscape")
{
application.executeProgramInBackground('lp', '-o
landscape', theTempFile);
}
else
{
application.executeProgramInBackground('lp', theTempFile);
}
}
Have you ever needed to update a column for all records in your foundset with a
given value? Perhaps you need to set a flag on all records signifying that they
have been “approved” or perhaps you need to timestamp them all with the
current date and time. Servoy’s foundset updater can come in handy in these
situations, allowing you to update the records in a flash and save you the trouble
of writing a for loop. Here’s an example where we quickly update the
ship_date column on all orders in the current foundset with the current time
and date:
The big advantage of this method, as compared with writing your own for loop,
is that this command is issued as a single SQL “update” statement instead of an
update for each row in your current foundset. This is what makes it run
comparatively quickly.
When you create your own database tables in your SQL backend of choice using
the Servoy interface, it’s likely that you’ll make an auto-incrementing numeric
primary key for each table you create. Although this is by far the most common
way of uniquely identifying each record in the table, it isn’t the only way. SQL can
use a combination of values from different fields to create the key for the row.
You may run across this when using Servoy to connect to a pre-existing data
source or to the backend of some piece of proprietary software that uses a
database for storage.
To work with this type of scenario in Servoy, make sure that all of the columns
that make up the key are marked as such in the Dataproviders dialog (sequence
set to “db identity”). Once this is done, all of the normal databinding that happens
by default will work (updates, searches, etc.). The catch comes if you plan to use
controller.loadRecords() in your methods. As described above, there are
a few ways use this function, but both work by passing in primary keys to be
loaded into the form. To load a multi-keyed row, we need to select both columns
and – importantly – we need to pass them to the loadRecords function in
alphabetical order. For a table with two keys some_key and another_key, the
method would look like this:
controller.loadRecords(theQuery, [“Mike”]);
One of the things that makes Servoy such a flexible tool in working with SQL
databases is that you, as the developer, always have the choice to work through
the interface or outside of it. You can take advantage of Servoy’s built-in
databinding by modifying records and searching using a particular form, or, if
you’d rather, write your own SQL and query the tables directly. The question of
which approach to use comes up often when you need to iterate over a group of
records. If you wish to work within the interface to change a certain value, you
could simply change the selected index and make your change to each row:
This approach can be quicker and is safer if your looping method may actually
add records to the foundset. It is generally used for changes to smaller numbers
of records and is especially useful for modifying a related foundset, where you
Servoy’s built-in history buttons and corresponding history object make moving
backwards and forwards between previously visited forms a breeze. Using the
method editor, we can even access the names of previously visited forms. While
these forms may not have names that are comprehensible to the user, you could
easily create a method to show a list of visited form names to the user.
Here’s a method that gets all of visited form names, matches them to a
corresponding display name (stored in a database table), and puts them in an
array which could be used to create an HTML menu:
Even though this type of history is highly useful, databases with hierarchical data
structures can sometimes benefit from a different type of “history” that takes into
account not only where the user has been, but where it fits into the database
structure as a whole. This technique, known as “breadcrumbs” to web designers,
helps users to have a better concept of where they are, gives them easy access
to other points higher in the hierarchy and helps them understand the logic of the
application. Here is a global method that accepts pairs of display names/actual
form names and creates HTML breadcrumbs that will allow users to navigate to
other forms.
theHTML += '</td></tr></table></body></html>';
globals.breadcrumb_display_field = theHTML;
On clicking one of the links, this global method would be called and take the user
to the requested form: