4D v16.3

Project Methods

Home

 
4D v16.3
Project Methods

Project Methods  


 

 

Project methods are aptly named. Whereas form and object methods are bound to forms and objects, a project method is available anywhere; it is not specifically attached to any particular object of the database. A project method can have one of the following roles, depending on how it is executed and used:

  • Menu method
  • Subroutine and function
  • Process method
  • Event catching method
  • Error catching method

These terms do not distinguish project methods by what they are, but by what they do.

A menu method is a project method called from a custom menu. It directs the flow of your application. The menu method takes control—branching where needed, presenting forms, generating reports, and generally managing your database.

The subroutine is a project method that can be thought of as a servant. It performs those tasks that other methods request it to perform. A function is a subroutine that returns a value to the method that called it.

A process method is a project method that is called when a process is started. The process lasts only as long as the process method continues to execute. For more information about processes, see the chapter Processes. Note that a menu method attached to a menu command whose property Start a New Process is selected, is also the process method for the newly started process.

An event catching method runs in a separate process as the process method for catching events. Usually, you let 4D do most of the event handling for you. For example, during data entry, 4D detects keystrokes and clicks, then calls the correct object and form methods so you can respond appropriately to the events from within these methods. In other circumstances, you may want to handle events directly. For example, if you run a lengthy operation (such as For...End for loop browsing records), you may want to be able to interrupt the operation by typing Ctrl-Period (Windows) or Cmd-Period (Macintosh). In this case, you should use an event catching method to do so. For more information, see the description of the command ON EVENT CALL.

An error catching method is an interrupt-based project method. Each time an error or an exception occurs, it executes within the process in which it was installed. For more information, see the description of the command ON ERR CALL.

A menu method is invoked in the Application environment when you select the custom menu command to which it is attached. You assign the method to the menu command using the Menu editor. The menu executes when the menu command is chosen. This process is one of the major aspects of customizing a database. By creating custom menus with menu methods that perform specific actions, you personalize your database. Refer to the 4D Design Reference manual for more information about the Menu editor.

Custom menu commands can cause one or more activities to take place. For example, a menu command for entering records might call a method that performs two tasks: displaying the appropriate input form, and calling the ADD RECORD command until the user cancels the data entry activity.

Automating sequences of activities is a very powerful capability of the programming language. Using custom menus, you can automate task sequences and thus provide more guidance to users of the database.

When you create a project method, it becomes part of the language of the database in which you create it. You can then call the project method in the same way that you call 4D’s built-in commands. A project method used in this way is called a subroutine.

You use subroutines to:

  • Reduce repetitive coding
  • Clarify your methods
  • Facilitate changes to your methods
  • Modularize your code

For example, let’s say you have a database of customers. As you customize the database, you find that there are some tasks that you perform repeatedly, such as finding a customer and modifying his or her record. The code to do this might look like this:

  ` Look for a customer
 QUERY BY EXAMPLE([Customers])
  ` Select the input form
 FORM SET INPUT([Customers];"Data Entry")
  ` Modify the customer's record
 MODIFY RECORD([Customers])

If you do not use subroutines, you will have to write the code each time you want to modify a customer’s record. If there are ten places in your custom database where you need to do this, you will have to write the code ten times. If you use subroutines, you will only have to write it once. This is the first advantage of subroutines—to reduce the amount of code.

If the previously described code was a method called MODIFY CUSTOMER, you would execute it simply by using the name of the method in another method. For example, to modify a customer’s record and then print the record, you would write this method:

 MODIFY CUSTOMER
 PRINT SELECTION([Customers])

This capability simplifies your methods dramatically. In the example, you do not need to know how the MODIFY CUSTOMER method works, just what it does. This is the second reason for using subroutines—to clarify your methods. In this way, your methods become extensions to the 4D language.

If you need to change your method of finding customers in this example database, you will need to change only one method, not ten. This is the next reason to use subroutines—to facilitate changes to your methods.

Using subroutines, you make your code modular. This simply means dividing your code into modules (subroutines), each of which performs a logical task. Consider the following code from a checking account database:

 FIND CLEARED CHECKS ` Find the cleared checks
 RECONCILE ACCOUNT ` Reconcile the account
 PRINT CHECK BOOK REPORT ` Print a checkbook report

Even for someone who doesn’t know the database, it is clear what this code does. It is not necessary to examine each subroutine. Each subroutine might be many lines long and perform some complex operations, but here it is only important that it performs its task.

We recommend that you divide your code into logical tasks, or modules, whenever possible.

You’ll often find that you need to pass data to your methods. This is easily done with parameters.

Parameters (or arguments) are pieces of data that a method needs in order to perform its task. The terms parameter and argument are used interchangeably throughout this manual. Parameters are also passed to built-in 4D commands. In this example, the string “Hello” is an argument to the ALERT command:

 ALERT("Hello")

Parameters are passed to methods in the same way. For example, if a method named DO SOMETHING accepted three parameters, a call to the method might look like this:

 DO SOMETHING(WithThis;AndThat;ThisWay)

The parameters are separated by semicolons (;).

In the subroutine (the method that is called), the value of each parameter is automatically copied into sequentially numbered local variables: $1, $2, $3, and so on. The numbering of the local variables represents the order of the parameters.

  //Code of the method DO SOMETHING
  //Assuming all parameters are of the text type
 C_TEXT($1;$2;$3)
 ALERT("I received "+$1+" and "+$2+" and also "+$3)
  //$1 contains the WithThis parameter
  //$2 contains the AndThat parameter
  //$3 contains the ThisWay parameter

Within the subroutine, you can use the parameters $1, $2... in the same way you would use any other local variable. However, in the case where you use commands that modify the value of the variable passed as parameter (for example Find in field), the parameters $1, $2, and so on cannot be used directly. You must first copy them into standard local variables (for example: $myvar:=$1).

Advanced note: 4D project methods accept a variable number of parameters of the same type, starting from the right. To declare these parameters, you use a compiler directive to which you pass ${N} as a parameter, where N specifies the first parameter. For example, the declaration C_LONGINT(${5}) tells 4D and the compiler that starting with the fifth parameter, the method can receive a variable number of parameters of longint type. Using the Count parameters command you can then address those parameters with a For loop and the parameter indirection syntax. For more information, please refer to the example 2 of the Count parameters command.

Depending on their type, parameters are passed by copy or by reference:

  • When a parameter is passed by copy, the local variables/parameters are not the actual fields, variables, or expressions passed by the calling method; they only contain the values that have been passed. Since its scope is local, if the value of a parameter is modified in the subroutine, it does not change the value in the calling method.
  • When a parameter is passed by reference, the local variables/parameters contain references that point to the actual source fields, variables, or expressions passed by the calling method; modifiying the value of the local parameter will modify the source value.

The following table shows how the different types of elements can be passed:

Type of parameterHow passedComment
Field, variable or expression of a scalar type (number, text, date...)by valueCan be passed by reference through a pointer, see below
Field, variable or expression of type Objectby referenceSee example below
Variable or expression of type Collectionby reference
Variable or expression of type Pointerby referenceSee Passing Pointers to Methods
ArrayCannot be passed directly as parameterCan be passed by reference through a pointer, see Arrays and Pointers
TableCannot be passed directly as parameterCan be passed by reference through a pointer, see Pointers

When using fields, variables and expressions of the scalar type as method parameters, only copies of values are passed.

Since $1, $2... are local variables, they are available only within the subroutine and are cleared at the end of the subroutine. For this reason, a subroutine cannot change the value of the actual fields or variables passed as parameters at the calling method level. For example:

  ` Here is some code from the method MY METHOD
  ` ...
 DO SOMETHING([People]Last Name) ` Let's say [People]Last Name is equal to "williams"
 ALERT([People]Last Name)
 
  ` Here is the code of the method DO SOMETHING
 $1:=Uppercase($1)
 ALERT($1)

The alert box displayed by DO SOMETHING will read “WILLIAMS” and the alert box displayed by MY METHOD will read “williams”. The method locally changed the value of the parameter $1, but this does not affect the value of the field [People]Last Name passed as parameter by the method MY METHOD.

There are two ways to make the method DO SOMETHING change the value of the field:

1. Rather than passing the field to the method, you pass a pointer to it, so you would write:

  ` Here is some code from the method MY METHOD
  ` ...
 DO SOMETHING(->[People]Last Name) ` Let's say [People]Last Name is equal to "williams"
 ALERT([People]Last Name)
 
  ` Here the code of the method DO SOMETHING
 $1->:=Uppercase($1->)
 ALERT($1->)

Here the parameter is not the field, but a pointer to it. Therefore, within the DO SOMETHING method, $1 is no longer the value of the field but a pointer to the field. The object referenced by $1 ($1-> in the code above) is the actual field. Consequently, changing the referenced object goes beyond the scope of the subroutine, and the actual field is affected. In this example, both alert boxes will read “WILLIAMS”.

For more information about Pointers, see the section Pointers.

2. Rather than having the method DO SOMETHING “doing something,” you can rewrite the method so it returns a value. Thus you would write:

  ` Here is some code from the method MY METHOD
  ` ...
 [People]Last Name:=DO SOMETHING([People]Last Name) ` Let's say [People]Last Name is equal to "williams"
 ALERT([People]Last Name)
  ` Here the code of the method DO SOMETHING
 $0:=Uppercase($1)
 ALERT($0)

This second technique of returning a value by a subroutine is called “using a function.” This is described in the next paragraphs.

When using variables, expressions, or fields of the object or collection type as method parameters, references to actual source values are passed. In this case, $1, $2... do not contain values but references. Modifying the value of the $1, $2... parameters within the subroutine will be propagated wherever the source object or collection is used. This is the same principle as for pointers, except that $1, $2... parameters do not need to be dereferenced in the subroutine.

For example:

  //The CreatePerson method creates an object and sends it as a parameter
 C_OBJECT($person)
 $person:=New object("Name";"Smith";"Age";40)
 ChangeAge($person)
 ALERT(String(OB get($person;"Age")))

  //The ChangeAge method adds 10 to the Age attribute of the received object
 C_OBJECT($1)
 OB SET($1;"Age";OB Get($1;"Age")+10)
 ALERT(String(OB get($1;"Age")))

If you execute the CreatePerson method, both alert boxes will read "50" since the same reference is handled by both methods.

4D Server: When parameters are passed between methods that are not executed on the same machine (using for example the Execute on Server option, see Project method properties), references are not usable. In these cases, copies of object and collection parameters are sent instead of references.

Data can be returned from methods. A method that returns a value is called a function.

4D or 4D Plug-in commands that return a value are also called functions.

For example, the following line is a statement that uses the built-in function, Length, to return the length of a string. The statement puts the value returned by Length in a variable called MyLength. Here is the statement:

 MyLength:=Length("How did I get here?")

Any subroutine can return a value. The value to be returned is put into the local variable $0.

For example, the following function, called Uppercase4, returns a string with the first four characters of the string passed to it in uppercase:

 $0:=Uppercase(Substring($1;1;4))+Substring($1;5)

The following is an example that uses the Uppercase4 function:

 NewPhrase:=Uppercase4("This is good.")

In this example, the variable NewPhrase gets “THIS is good.”

The function result, $0, is a local variable within the subroutine. It can be used as such within the subroutine. For example, in the previous DO SOMETHING example, $0 was first assigned the value of $1, then used as parameter to the ALERT command. Within the subroutine, you can use $0 in the same way you would use any other local variable. It is 4D that returns the value of $0 (as it is when the subroutine ends) to the called method.

Project methods can call themselves. For example:

  • The method A may call the method B which may call A, so A will call B again and so on.
  • A method can call itself.

This is called recursion. The 4D language fully supports recursion.

Here is an example. Let’s say you have a [Friends and Relatives] table composed of this extremely simplified set of fields:
- [Friends and Relatives]Name
- [Friends and Relatives]ChildrensName

For this example, we assume the values in the fields are unique (there are no two persons with the same name). Given a name, you want to build the sentence “A friend of mine, John who is the child of Paul who is the child of Jane who is the child of Robert who is the child of Eleanor, does this for a living!”:

1. You can build the sentence in this way:

 $vsName:=Request("Enter the name:";"John")
 If(OK=1)
    QUERY([Friends and Relatives];[Friends and Relatives]Name=$vsName)
    If(Records in selection([Friends and Relatives])>0)
       $vtTheWholeStory:="A friend of mine, "+$vsName
       Repeat
          QUERY([Friends and Relatives];[Friends and Relatives]ChildrensName=$vsName)
          $vlQueryResult:=Records in selection([Friends and Relatives])
          If($vlQueryResult>0)
             $vtTheWholeStory:=$vtTheWholeStory+" who is the child of "+[Friends and Relatives]Name
             $vsName:=[Friends and Relatives]Name
          End if
       Until($vlQueryResult=0)
       $vtTheWholeStory:=$vtTheWholeStory+", does this for a living!"
       ALERT($vtTheWholeStory)
    End if
 End if

2. You can also build it this way:

 $vsName:=Request("Enter the name:";"John")
 If(OK=1)
    QUERY([Friends and Relatives];[Friends and Relatives]Name=$vsName)
    If(Records in selection([Friends and Relatives])>0)
       ALERT("A friend of mine, "+Genealogy of($vsName)+", does this for a living!")
    End if
 End if

with the recursive function Genealogy of listed here:

  ` Genealogy of project method
  ` Genealogy of ( String ) -> Text
  ` Genealogy of ( Name ) -> Part of sentence
 
 $0:=$1
 QUERY([Friends and Relatives];[Friends and Relatives]ChildrensName=$1)
 If(Records in selection([Friends and Relatives])>0)
    $0:=$0+" who is the child of "+Genealogy of([Friends and Relatives]Name)
 End if

Note the Genealogy of method which calls itself.

The first way is an iterative algorithm. The second way is a recursive algorithm.

When implementing code for cases like the previous example, it is important to note that you can always write methods using iteration or recursion. Typically, recursion provides more concise, readable, and maintainable code, but using it is not mandatory.

Some typical uses of recursion in 4D are:

  • Treating records within tables that relate to each other in the same way as in the example.
  • Browsing documents and folders on your disk, using the commands FOLDER LIST and DOCUMENT LIST. A folder may contain folders and documents, the subfolders can themselves contain folders and documents, and so on.

Important: Recursive calls should always end at some point. In the example, the method Genealogy of stops calling itself when the query returns no records. Without this condition test, the method would call itself indefinitely; eventually, 4D would return a “Stack Full” error becuase it would no longer have space to “pile up” the calls (as well as parameters and local variables used in the method).



See also 

Control Flow
Database Methods
Methods

 
PROPERTIES 

Product: 4D
Theme: Language definition

 
HISTORY 

 
ARTICLE USAGE

4D Language Reference ( 4D v16)
4D Language Reference ( 4D v16.1)
4D Language Reference ( 4D v16.2)
4D Language Reference ( 4D v16.3)