Tech News Back Issues Issue: 011305
Report Configuration Using the $construct Method
In Omnis 7, if we wanted to dynamically modify some aspect of a report based on some state of the application or of the data, we had to modify the Report Format, or objects within it, from a procedure external to that report - a procedure either in a Window Format or a Menu Format since those were the only places procedures could exist. When we needed different report layouts for the same data set - for example, detail and summary versions of a report - we would simply create a separate Report Format for each version of the report. This scheme would work fine until we decided to update some aspect of that report that required the same or related modifications to all the variations, at which point we would have to make those modifications to each Report Format (hoping that we didn't forget any and that we made the changes consistently for each one).
Today in Omnis Studio our Report Classes can contain methods just like Window and Menu Classes from earlier Omnis generations. And as with all instantiable classes in Omnis Studio, Report Classes can contain a specialized method named $construct. This method is executed as part of the report instantiation process, allowing the report instance to reconfigure itself to fit current circumstances (among other things). We will look at a couple of simple ways in which we can use this new capability in this article.
When a Class of any type contains all the methods needed to control all aspects of its instances (except, perhaps, the spawning of those instances), we say that it is encapsulated. Such a Class controls its own destiny - or, at least, the destinies of its instance offspring. Methods within an instance of such a Class can communicate with the outside world through messaging, but messages can also be sent to that instance - including a message to perform the $construct method (which is sent implicitly when the instance is spawned). And the message can contain content in the form of parameters that help the instance understand what is expected of it.
We will explore one small but significant portion of encapsulation in this article: self-configuration or encapsulated setup processes. In future articles, we will look into other processes we can perform with $construct.
Our primary means of receiving setup information from the method that spawns a report instance are the parameters of the report's $construct method. Parameter variables have the same scope as local variables, so they only exist during the execution of the $construct method and are not in scope for methods invoked as subroutines of that method. If we want the value passed to a parameter of a $construct method to be available throughout our report instance, we need to transfer that value to an instance variable. Not all constructor parameters require this - only those that are to be used within subsequent process of the instance beyond construction. Said another way: If the passed value is only needed for setup purposes (or other things which can be completely handled in $construct), we do not need to define an instance variable for it as well as a parameter variable.
To pass the value of a parameter to an instance variable, we simply:
In those cases where this value is needed both for setup purposes in $construct and elsewhere within the instance, there is a trick we can use to eliminate this explicit calculation step. The details of this technique, with an explanation of why it works and an experiment to prove that this is how Omnis Studio works, are found in my book "Variables and Fields". But here is a quick summary:
As long as the instance variable is used in some meaningful way (not just mentioned in a comment) in $construct, we can simply assign a parameter variable from that method as the initial value of the instance variable. Unlike parameters, which come into existence (are allocated RAM space and assigned the values passed to them) when the method to which they belong begins execution, local and instance variables only come into existence when they are first used. If we use our instance variable within $construct, then parameters of that method are in scope and will have a value when the instance variable comes into being. If the instance variable is not used within $construct but we set a constructor parameter as its initial value (which we can only do while $construct is the current method in the Method Editor, by the way), then the parameter will not contain a value (because it doesn't even exist!) and the instance variable would be assigned an empty value as its default. This applies to instances of any instantiable class - not just reports.
Setting Report Instance Property Values
One of the most basic items we can modify in the $construct method of a report is a property value of the Report Instance. To dynamically set the value of a property of an instance of a report, we can pass a parameter to $construct and then assign the property value using the parameter variable. We can do this with many properties of the report instance, but here are a few to pique you interest:
Changes to properties that encompass the entire instance must be performed before any records are processed for printing. Otherwise, the instance has progressed to far in its incubation process to accept a property change. In this article we are only addressing reports that use the $mainfile or $mainlist setting for gathering record images to print (with no data manipulation in $construct itself), so this is not an issue. But in future articles where we do more interesting things in $construct, this is an important rule to understand. Ignorance of the rule does not keep its effects from occurring...
Page properties ($orientation, for example) can be modified at any time during the printing of the report, but a changed value will only apply to the next page to be printed, not the one that is currently being laid out. This is because the Virtual Page has already been defined and its properties cannot then be changed. Page properties for the first page of a report instance (which subsequent pages will also use until the property value is changed) can be set in $construct following the rule in the previous paragraph. Here are some page properties that work in this way:
We can pass parameters to a report instance using any of the commands that we would normally use for printing a report. Because of the type of report we are using in this article (Main File or Main List report), we will use the Print report command to demonstrate this. This command gives us the option of passing parameters to the report's $construct method by enclosing a comma-delimited list of them within parentheses in the appropriate place within the command parameters:
Here we see both the final form of the Print report command with a parameter to be passed and the detail block for that command showing how the parameter is set up to be passed. The Prepare for print command sends parameters in the same way, while notational techniques for launching a report instance pass parameters as usual.
That's all the preliminary information we need, now let's look at a quick example.
Number of Labels Example
Suppose we have a mailing label report and from somewhere within the application we want our users to decide how many labels will be printed per record. We may not do this at every access point to this report, but we do it from at least one location. Since we may have some locations that launch this report without passing a parameter, we need to set an initial value for this parameter to act as a default value when no parameter is passed. Let's say that our default value for the number of labels to pring per record is 1.
Setting up an initial value for a scoped variable is easy! We just supply an expression in the Init. Val/Calc column of the variable definition as shown here:
In this case, the expression is simply a numeric value.
To use this parameter to modify the $repeatfactor property value, we simply perform a calculation in the $construct method:
Then our label report will repeat the Record section the number of times the user specified using whatever interface we supplied for that purpose.
Complicating a Perfectly Good Simple Example
But that's just too simple, so let's add another dimension. Suppose that we want to number each label in the form "x of y" (with "x" being the label count within the group for a record and "y" being the number of labels per record), but only print this is the user so chooses. We can't do all this from within the $construct method (at least, not with the reporting technique we are using in this article), so we now need an instance variable for the label count so we can use that value elsewhere within the report. Furthermore, we need a second parameter to carry the user's choice as to whether to print this value or not. Let's tackle these items separately.
First, getting the user's choice for number of labels per record (or the default) available throughout the instance. We need an instance variable for this purpose, so we define one with the same datatype as our parameter. Since we still need to set the $repeatfactor for the instance using this value, we can set the initial value for the instance variable to be the $construct parameter (which must be done while $construct is the currently selected method in the Method Editor). Let me emphasize: I am not saying that we must do it this way; I am merely demonstrating that we can do it this way. Our instance variable definition will then look like this:
And our $construct method can now use the instance variable rather than the parameter variable like this:
The instance variable must be created in order to be used and setting it's initial value is part of that process. Parameter variables, on the other hand, are created when the method first launches since they must be ready to "catch" a value or reference sent to the method. So the parameter value is available for initializing the instance variable when that time comes. We can test this either by changing the initial (default) value of the pLabelCount parameter variable or by building a simple window that allows the user to enter a value into a field (representing an instance variable of the window perhaps named userLabelCount) and then creating a pushbutton field (or some other control) that uses the Print report command as shown eralier in this article to launch the report. This exercise is left up to the ingenuity of the reader.
Getting the "x of y" display in each label is an interesting project as well. We need to display the result of a calculation of the form:
The trick is to determine how to get the number of labels printed so far. We can't use $cinst.$reccount because that is only the number of records that have been printed, so it will have the same value for each label in a set. We need something that will increment for each label that prints. To do this, we need to define another instance variable. We will call this one counter and give it the Long integer datatype with no initial value (which will then default to 0). We can then place an invisible field (we don't want to see it on the printed labels) in our Record section somewhere above the field that is to display the "x of y" value, give that field counter as its dataname value, set its calculated value to kTrue and set its text value to counter+1. Since this field gets processed each time a label image is placed on the Virtual Page, the associated variable is incremented for each label printed! So this is the actual print count rather than the record count. The resulting report can then look like this (passing a value of 3):
But we only want to print this additional text on the label if the user so chooses, so we must give them the option on our launcher window and then pass in a second parameter. If we expand the calculation of the optional field on the label to choose between the current expression or an empty string, we would need this parameter's value throughout the instance, so would have to create an instance variable for this too. (Of course, we could simply use the parameter to make the field invisible in this instance, but we'll look at that possibility in a moment.) So we would define a parameter for $construct named pShowLabelCount and an instance variable named showLabelCount, both of Boolean datatype. We would give the parameter variable a default value of kFalse for those cases where the report is printed without these choices given to the user (as well as for testing purposes) and would change our $construct method to read:
Notice that we calculate the instance variable as the constructor parameter here because there is nothing else we want ot do with the parameter value at this point. In this case we would use the following expression as our text value in the counter field:
If we choose to simply make the "x of y" field invisible unless the user chooses to display it, we could leave this expression the way it was, not define a showLabelCount instance variable and instead change our $construct method to read:
Arguments can be made for either choice, but this leads into our next topic...
I hope this has been of some benefit to you. In the next issue of Omnis Tech News we will expand on the use of the $construct method of a report further and look at how we can modify positions and other attributes of component objects within a report to give different faces (as in various levels of detail) to the same data set.
Copyright of the text and images herein remains with the respective
author. No part of this newsletter may be reproduced, transmitted,
stored in a retrieval system or translated into any language in any
form by any means without the written permission of the author or
Omnis® and Omnis Studio® are registered trademarks, and Omnis 7 is a trademark of Raining Data UK Ltd. Other products mentioned are trademarks or registered trademarks of their corporations. All rights reserved.