New in Xbasic

Debugger Features

The debugger has been completely rewritten. Many new features have been added. It is now substantially faster and it will dramatically increase your productivity when you are debugging your Xbasic code.

Examine Variables

You can now examine a variable in your script by simply hovering the mouse over the variable. The window will go away when you click the mouse on the debugger background. You can also drag this window into the Watch window to add the variable to the Watch list. You can also double click on a variable to quickly add it to the Watch window.

In this example, the mouse is hovering over 'p2'. The pop-up window can be locked by clicking on the icon on the top right. You can edit values in the running script by clicking on the pencil icon. You can navigate up and down in dot variables by clicking on the 'bread crumbs' in the title bar.

Thread Window

The Thread window allows debugging of background threads. The window displays a list of running threads. Use this to debug scripts that use the thread_create() function. You can hold the mouse over an entry in the Thread Window to see the call stack for that thread.

Stack Window

Allows you to see the Xbasic stack (i.e. the list of prior functions that have been called). You can hold you mouse over the icon to the left of each entry to see the variables at that level of the stack.

This simplistic example shows a script that calls a function foo(), which in-turn calls another function foo2(). We set a break point inside foo2() so the Stack window shows foo() and foo2() on the stack.

 

Watch Window

This image shows how an array and a dot variable can be examined in the Watch window. Notice that the array has been expanded to show all of the array entries. The dot variable has also been expanded to show its properties. The next image shows how these variables appear in the Watch window in their collapsed state.

 

 

Log Window

You can log changes to a variable during the progression a script. To turn on the feature, right click on a variable and select the 'Log change' menu item.

 

To open the Log Window, select the View menu.

 

 

Notice that the top of the Log window is an outline of scripts, list of line numbers  and expressions to log. The log line can contain multiple expressions. You simply need to separate the entries with a comma - e.g. "i,list" to log changes in both the 'i' and 'list' variable on the given line,

In the lower pane, the currently logged values are shown. If the content is non-trivial (i.e. bigger than 128 characters, including CR-LF characters, or if it is a property variable) - then an external 'link' (a pencil icon) is provided to inspect it. The link is to an external file in the log folder (which is inside the temp folder).

In the above example, clicking on the pencil icon for the 'person' variable will bring up this window:

 

 

Profiler

You can now profile a section of code. Previously profiling was only available by inserting profiler_begin()/profiler_end() Xbasic commands into your code. You can now right click on any line of code and select the Begin Profiling or End Profiling command.

To examine the profiling output, select the Profiling Window from the View menu.

 

Real time comment-out

You can now comment out certain lines of code in your script while you are debugging by right clicking on a line and selecting the Comment out line command. The debugger shows an icon next to the commented out line. In the screenshot below, the debug(1) command has been commented out.

 



 

 

Edit Breakpoint Window

You can now edit all breakpoints in your debugging environment from a single window. You can also assign conditions to breakpoints. For example, the screenshot below shows that the breakpoint on line 8 will trigger when the variable, 'i' is equal to 8.

You can also disable breakpoints (rather than removing them entirely).

 

Conditional Breakpoints and Global Breakpoints

Conditional breakpoints allows you set a condition expression for a breakpoint that you have set in your code, or for a global breakpoiont. The breakpoint will only be triggered if the condition is true. To set a condition for a breakpoint, right click on the breakpoint in the Script Window and select the Edit Breakpoint command. This will open the Edit Breakpoint Window where you can set a logical condition for the breakpoint. Notice that the icon for conditional breakpoints changes to a red ball with a while plus sign in it.

A global breakpoint allows you to break the running script when an expression's value changes, or when an expression is true. This is extremely useful if you want to find out where a particular variable in your code is getting changed, or set to a certain value, and you are not sure which line of code is changing the variable.

To set a global breakpoint, open the Breakpoint Window, type an expression into the Expression box and then click the Add Global Break button. If you want to break when the expression changes value (as opposed to when a particular condition is true), check the Break on Change box before you click the Add Global Break button.

In the image shown below, you see two global breakpoints. The first entry causes the running script to break when the value of the variable 'customerName' changes. The second breakpoint causes the running script to break when the expression 'city="Boston"' becomes true.

 

Configuring the Toolbar

You can configure the buttons on the Debugger toolbar. To do this, select View, Configure Toolbar command.


 

 

Classes

Version 10  makes it easier to define and use classes in your applications.

When you open the Script Editor, you will notice that the toolbar has two new icons (4th and 5th icon in the image below) for creating new classes and new enumerations.

 

The section below is a short introduction to classes.

Introduction to Classes

The purpose of a class definition is to encapsulate data with the methods that operate on the data.

For our first simple example, let us assume that you want to create a class that represents a person, and includes name, gender and marital status. The class we will define has a method to validate, a method to edit the contents, and a method to generate a full name.

Here is our example code (you can paste this into a new Xbasic script  - paste it into a script not a class):
 


define class personInformation
dim first_name as c
dim last_name as c
dim gender as c
dim married as l = .f.
 

function Validate as l()
if self.first_name = "" then
    ui_msg_box("Error","Firstname is empty.")
    Validate = .f.
    exit function
end if
if self.last_name = "" then
    ui_msg_box("Error","Lastname is empty.")
    Validate = .f.
    exit function
end if
if self.gender = "" then
    ui_msg_box("Error","Gender is not specified.")
    Validate = .f.
    exit function
end if
Validate = .t.
end function
 

function Edit as v()
ui_dlg_box("Edit Person",<<%dlg%
First Name:|[.40self.first_name];
Last Name:|[.40self.last_name];
Gender:|[40self.gender^={Male,Female}];
Married:|(married);
{line=2};
<*&OK>;
%dlg%)
end function
 

function FullName as c()
if self.gender = "Male" then
    FullName = "Mr " + first_name - " " + last_name
else if self.married then
    FullName = "Mrs " + first_name - " " + last_name
else
    FullName = "Ms " + first_name - " " + last_name
end if
end function
end class

 

Having defined the class, we can now use this class in the same script (paste this into the same script where you defined the class).


dim person as personInformation

person.gender = "Male"
person.Edit()
while .not. person.Validate()
person.Edit()
end while
ui_msg_box("Salutation","Hello "+person.FullName())

 

When you run the script that contains this class definition, you will see this dialog:

 

As you can see, once the class is dimmed, the properties and methods defined in the class are accessible.

Visibility -  Making a Class Visible Outside the Script that Defines It

Class definitions are similar to variables in that they have a scope, which defaults to local, but can be optionally specified as session, global, or system.

You will notice that after you run the script, if you go to the Interactive window and type:


dim person as personInformation
 


You will get an error:

ERROR: Variable not found 'person' (extended type 'personInformation').


This is because after the script runs, the class is no longer visible, since it was local to the script (just as the variables that were DIMmed locally to the script are not visible).

If you change first line of the script from:

define class personInformation
 

to:

define class global personInformation
 

and run the script again, then go to the Interactive window, and type:

dim person as personInformation

you will see that it works.

 

You have created a 'personInformation' object, and all of the object's properties and methods work, just as they did in the original script.

The scope of a class can be 'local', 'shared', 'global' or 'system'.

Creating a Class the Loads Automatically

To create a class that loads automatically, (rather than requiring your to first run the script that defines the class, as we have done in the prior examples), you create the class definition in the new Class Editor in V10 (rather than in a script, as we have done in the previous examples).

To open the Class Editor, open the Code Editor and click the New Class icon on the toolbar:


Even though a class can be defined in a script, the problem with doing this is that the script must be run before the class is available for use.

On the other hand, if you create your class in the Class Editor, the class will be will automatically be loaded when it is used.

To change our example so that the personInformation class is always available when needed, open the Class Editor, and paste the class definition from above into the editor.

(Note: Only paste the class definition, not the code that instantiates and uses the class!. The last line of code that you paste must be 'end class'.)

When you paste the code into the Class Editor, be sure that the scope of the Class is set to Global. The first line of the class definition should be:

define class global personInformation
 

Save the class with the same name as the class itself.

Now close the database and reopen it and then go to the Interactive window and type:

dim p as personInformation

 

Notice that it works! Alpha Five has automatically loaded the class into memory for you and you are able to use the class anywhere.

Static Methods - What They Are and How They are Used

A static class method is a method that does not require an 'instance' of the class to exist.

A good example of a static method is table.open(). The table.open() command creates an initialized table object that provides access to instance methods, like <table>.change_begin() to start changing a record, and <table>.close() - to close the table we opened.
 

You can create static methods in your Xbasic classes by using the [static] keyword in the function definition.

Taking our original example, we add a static method called 'createPerson' to the 'personInformation' class, which will let us create a person object and initialize some of its properties:
 

define class global personInformation
 

dim first_name as c
dim last_name as c
dim gender as c
dim married as l = .f.
 

function Validate as l()
if self.first_name = "" then
    ui_msg_box("Error","Firstname is empty.")
    Validate = .f.
    exit function
end if
 

if self.last_name = "" then
    ui_msg_box("Error","Lastname is empty.")
    Validate = .f.
    exit function
end if
 

if self.gender = "" then
    ui_msg_box("Error","Gender is not specified.")
    Validate = .f.
    exit function
end if
 

Validate = .t.
end function
 

function Edit as v()
ui_dlg_box("Edit person",<<%dlg%
First Name:|[.40self.first_name];
Last Name:|[.40self.last_name];
Gender:|[40self.gender^={Male,Female}];
Married:|(married);
{line=2};
<*&OK>;
%dlg%)
end function
 

function FullName as c()
if self.gender = "Male" then
    FullName = "Mr " + first_name - " " + last_name
else if self.married then
    FullName = "Mrs " + first_name - " " + last_name
else
    FullName = "Ms " + first_name - " " + last_name
end if
end function
 

function [static] CreatePerson as p(fname as c,lname as c,gender as c)
dim person as personInformation
person.first_name = fname
person.last_name = lname
person.gender = gender
CreatePerson = person
end function
 

end class
 

Now in the interactive window, we can create a new person object by using the 'personInformation.CreatePerson()' method, for example:
 

pd = personInformation.CreatePerson("John","Public","Male")
? pd.first_name
= "John"

pd.edit()
 

Using the static method of the personInformation class, we have created a new instance of that class and initialized some of its properties. Alternatively, we could have done this (which does not use the static method of the Class):

dim pd as personInformation

pd.first_name = "John"

pd.last_name = "Public"

pd.gender  = "Male"

pd.edit()

 

Inheritance of Classes

Inheritance is the concept of taking one class definition, and extending it to add new data and methods. If we take our example class of personInformation, an example of extending personInformation might be to create an 'employeeInformation' class which is built on the personInformation class, but adds a 'salary' property.


Here is an example that extends the personInformation class to create a new class called employeeInformation:
 

define class global employeeInformation inherits personInformation
 

dim salary as n
 

function Edit as v()
ui_dlg_box("Edit employee",<<%dlg%
First Name:|[.40self.first_name];
Last Name:|[.40self.last_name];
Gender:|[40self.gender^={Male,Female}];
Married:|(married);
Salary:|[40self.salary];
{line=2};
<*&OK>;
%dlg%)
end function
 

function [static] CreateEmployee as p(fname as c,lname as c,gender as c,salary as n)
dim employee as employeeInformation
employee.first_name = fname
employee.last_name = lname
employee.gender = gender
employee.salary = salary
CreateEmployee = employee
end function
 

end class
 

 

In the interactive window, we can now use employeeInformation - note that the FullName() method that personInformation defines is available to the employeeInformation class.
 


dim e as employeeInformation
e = employeeInformation.CreateEmployee("Sam","Smith","Male",70000)
? e.FullName()
= "Mr Sam Smith"

? e.salary
= 70000
 

Enumerations

An Enumeration is a means of a means of representing numeric values with friendly 'symbolic' names.

Llike classes, enumerations have a scope which can be local to a script, shared, global or system.


enum global myEnum
    one = 1
    two = 2
    three = 3
end enum
 

Then go to the interactive window, you will now be able to dim a new numeric type:
 

?two

= 2

 

UTF-8 Support

Alpha Five now has improved support for UTF-8 characters.

All data that is accessed from SQL databases using AlphaDAO are now returned in UTF-8 form. Previously, data accessed from SQL databases was translated to ansi format.

When using data from SQL databases in Xdialogs, you will need to be conscious of whether of whether the data is in UTF-8 form or not. The new {encoding=utf8} and {encoding=ascii} can be used to automatically convert data when it is displayed in an Xdialog. See What's New in Xdialog for more details.

You can also use the convert_utf8_to_acp() function to convert data from UTF-8 to ansi format. The corresponding convert_acp_to_utf8() function converts from ansi to UTF-8.

 

convert_utf8_to_acp() Function

Convert data from UTF-8 to ansi code page.

 

dim cn as sql::connection
?cn.open("{A5API=Access,FileName='C:\a5v10\MDBFiles\Northwind.mdb',UserName='Admin'}")
?cn.Execute("select * from customers where customerId = 'bergs'")
= .T.
rs = cn.ResultSet
x = rs.data("companyname")
?x
= "Berglunds snabbköp"
?convert_utf8_to_acp(x)
= "Berglunds snabbköp"

 

y = convert_utf8_to_acp(x)

?y
 

= "Berglunds snabbköp"

? convert_acp_to_utf8(y)

= "Berglunds snabbköp"
 

 

convert_acp_to_utf8() Function

Convert data from ansi code page to UTF-8 format.

See convert_utf8_to_acp() for example.

Sending E-mail Using Gmail SMTP Server

In V9, the email_send() function could not be used to send e-mail using the Gmail SMTP server. Now, this works. When you call email_send() and specify the Gmail server, the new email_send_gmail() function is actually called. The email_send_gmail() function uses the new EmailSendCDO() function to send the e-mail. This function uses the Microsoft CDO ActiveX control.

Email_send_gmail() function

Allows you to send e-mail using the Gmail SMTP server

Syntax:

L result = email_send_gmail(C gmail_email_address ,C gmail_email_password ,C to ,C from ,C subject ,C html_body ,C text_body [,C cc [,C bcc [,C attachments ]]])"
 

EmailSendCDO() Function

Allows you to send e-mail. Similar to the email_send() function, but uses the Microsoft CDO ActiveX control rather than Alpha Five's socket commands.

Synatx:

L result =  EmailSendCDO(C SmtpServer ,N SmtpPort ,C SmtpUsername ,C SmtpPassword ,L SmtpAuthenticate ,L SmtpUseSsl ,C To ,C From ,C Subject ,C HtmlBody ,C TxtBody [,C Cc [,C Bcc [,C Attachments ]]])"
 

For the Gmail SMTP server, set SmtpPort to 465, SmtpAuthenticate  = .t. and SmtpUseSsl  = .t.

 

inListN() Function

The InListN() Function tests to see if a value is in a list of values, and returns the index into the list. For example, if the item is the 3rd entry in the list, it returns 3. If the item is not found in the list, it returns 0

Syntax N index = InList(A lookFor, A value_1 [,  A value_2 [, A value3 .........)

Examples

?inListN(3,1,4,2,3)

= 4

?inListN(3.1,1,4,2,3)

= 0

?inListN("a","a","b","c")

= 1

 

*page() Function

*page() takes two arguments: starting logical record number (zero based) and 'page' size. It is designed for use in the table.external_record_content_get() function to limit the rows of data that are returned.

Example:

 

Return 3 records starting with the first logical record (record 0)

?table.external_record_content_get("customer","lastname + recno()","","*page(0,3)")
= Graham 1
Peabody 2
Rebo 3

 

Returns 4 records starting with the 8th logical record:

?table.external_record_content_get("customer","lastname + recno()","","*page(7,4)")
= Cario 8
McMiggan 9
Dubi 10
Winka 11
 

This example is more complex. It returns the data in the Javascript JSON notation for use in a Web application

dim fl as c
fl = comma_to_crlf("firstname,lastname,firstname-' '+lastname as fullname")
ce = "*json_record(" + s_quote(fl)+ ")"
?table.external_record_content_get("customer",ce,"bill_state_region"," bill_state_region='ca'.and.*page(0,5)")
= {firstname : 'Willy' , lastname : 'Winka' , fullname : 'Willy Winka'},
{firstname : 'Yvonne' , lastname : 'Harrington' , fullname : 'Yvonne Harrington'},
{firstname : 'Joan' , lastname : 'McAndrews' , fullname : 'Joan McAndrews'},
{firstname : 'Leonard' , lastname : 'Burtonski' , fullname : 'Leonard Burtonski'},
{firstname : 'Peter' , lastname : 'Harrison' , fullname : 'Peter Harrison'}

 

Ui_keycode_Normalize() Function

The OnKey event in Form Layouts expects the key code to be specified using a somewhat cryptic syntax. For example, the Ctrl-Shift-F12 key combination is expressed as: {^+F12}. The order of the modifiers (e.g. ^ + and %) is significant and it can be hard to know what the correct order is.

 

The ui_keycode_normalize() function allows you to convert from a friendly syntax for expressing a key combination to the cryptic syntax expected by the OnKey event handler. The following Interactive Window session demonstrates the function:

 

? ui_keycode_normalize("{Ctrl-Shift-F12}")
= "{^+F12}"
 

? ui_keycode_normalize("{Shift-F12}")
= "{+F12}"

? ui_keycode_normalize("{Alt-F12}")
= "{%F12}"

? ui_keycode_normalize("{Alt-M}")
= "{%m}"
 

Here is how you could use this function when you write an event handler for an OnKey event in a Form:


IF a_user.key.value = ui_keycode_normalize("{Ctrl-Shift-F12}") THEN
    a_user.key.handled = .T.
    IF a_user.key.event = "down" THEN
       ui_msg_box("Shift-Ctrl-F12 Pressed","Shift-Ctrl-F12 Pressed")
    END IF
END IF

 

 

a5_defineAliases() Function

Allows you to define aliases for use in a Desktop application using Xbasic. Aliases in a Desktop application are normally defined using the View/Settings/Aliases dialog. This function allows you to define the aliases programmatically.

The following script (which would typically be placed in the Autoexec) will define two new aliases:


dim myaliases as c
myaliases = <<%txt%
[server]|\\masterserver\images
[server2]|\\slaveserver\images2
%txt%
a5_defineAliases(myaliases)
 

 

Having defined the aliases, you can now use them, as shown below in the Interactive window:

?filename_decode("[server]\image1.jpg")
= "\\masterserver\images\image1.jpg"

 

 

Payflow Functions for PayPal

Two new functions, PayflowCredentials() and PayflowDelayedCapture() allow you to automate certain tasks associated with processing PayPal transactions.

PayflowCredentials() is used to set your credentials.

PayflowCapture() is used to execute a delayed capture on a specified PayPal transaction.

The following Interactive Window session shows how to use these functions:

 



dim Credentials as p
Credentials = PayflowCredentials("myusername","mypassword","myvendor","mypartner")

dim r as p
r = PayflowDelayedCapture(Credentials,"VPFA3CB5DCBF")

?r
= AUTHCODE = "023816"
AVSADDR = "X"
AVSZIP = "X"
CVV2MATCH = "Y"
HOSTCODE = "A"
PNREF = "VXJA3D3C2FF1"
PROCAVS = "S"
PROCCVV2 = "M"
RESPMSG = "Approved"
RESULT = "0"

 

 

If r.RESULT is 0, then the transaction was successful. A non-zero value indicates an error and .RESPMSG will give additional information
 

 

Summary of New Xbasic Functions

hasChildRecords(tableAlias) For use in a Form or Report that is based on a set with one or more one-to-many child tables. Returns .t. if the current parent record has any children in the specified tableAlias.
stritran_multi_expressions(String,SearchReplaceString) Does a search and replace operation in a string using a CR-LF delimited list of search and replace values. Contrast with the stritran_multi() function.
strtran_multi_expressions(String,SearchReplaceString) Case-sensitive version of stritran_multi_expressions()
inListN(lookfor, value1, value2, valuen) Returns the index (1,2,3, etc.) of the first value that matches lookfor. If there are no matches, returns 0. Function is case-sensitive.
convert_acp_to_utf8() Convert data from ansi code page to UTF-8.
convert_utf8_to_acp() Convert data from UTF-8 to ansi code page
email_send_gmail() Send e-mail using the Gmail SMTP server.
emailSendCDO() Use the Microsoft CDO ActiveX object to send e-mail rather than the Alpha Five built-in socket object.
*page(startingLogicalRecordNumber, pageSize) Limits the number of records returned by the table.external_record_content_get() function.
ui_keycode_normalize(FriendlyKeyCode) Converts a key code string (e.g. Shift-Ctrl-F12) to the syntax required by the OnKey event in a Form Layout.
A5_field_getKeyColumns(dbfTableName) Return the name of the 'primary key' columns of a .dbf table. .dbf tables do not use primary keys. However, you can infer the 'primary key' if the table has an auto-increment column, or a 'unique value' field rules.
A5_defineAliases(aliases) Takes a CRLF delimited list of aliases defined using this format: [aliasname]|path. Allows you to define aliases for use in a Desktop application using Xbasic. Aliases in a Desktop application are normally defined using the View/Settings/Aliases dialog. This function allows you to define the aliases programmatically.


 

Field Rules Now Honored During Xbasic Data Entry

When you do data entry using Xbasic, only a limited number of Field Rules (so called 'engine level' Field Rules) are honored. Rules for calculated fields and auto-increment fields are honored, but rules such as capitalization, validation, posting, cross-file validation, etc. are not honored. These Field Rules are only honored when you do data entry interactively, using a Form or Browse layout.

.enter_record(),  .change_record() and .delete_record() Methods

Now, three new methods, <tbl>.change_record(), <tbl>.enter_record() and <tbl>.delete_record() allow you to enter/edit/delete records using Xbasic, while still honoring Field Rules. These methods can be used instead of the older <tbl>.enter_begin() and <tbl>.chagne_begin() methods.

The example  below shows an Interactive Window session (assume that the table has defined Field Rules that specify that Lastname is required, and that the Company can't be 'alpha'):

t = table.open("customer")
'DIM a dot variable with properties that match the fieldnames you want to enter
dim r as p
r.firstname = ""
r.company = "alpha"

 

DIM v as p
v = t.enter_record(r)
?v.has_errors
= .T.
?v.error.size()
= 2

?v.Format("$(field) = $(error)"+crlf())
= CUSTOMER1->COMPANY = company can't be alpha
CUSTOMER1->LASTNAME = Field is required: LASTNAME
 

Notice from the above example that the .enter_record() method returns a dot variable that contains information about any Field Rule violations that occured.

Similarly, the example below shows how to use the .change_record() method:

 

'DIM a dot variable with properties that match the fieldnames you want to change
dim r as p
r.firstname = ""
r.company = "alpha"
v = t.change_record(r)
?v.has_errors
= .T.
?v.error.size()
= 2

?v.Format("$(field) = $(error)"+crlf())
= CUSTOMER1->COMPANY = company can't be alpha
CUSTOMER1->LASTNAME = Field is required: LASTNAME
 

As the examples show, a dot variable is created with properties for all of the fields that you want to enter/change. In the above examples, we enter or change the 'firstname' and 'company' fields in the customer table.

Once the dot variable has been created, the <tbl>.enter_record() or <tbl>.change_record() method is called. These methods take as their argument the dot variable with the field values.

The methods return a dot variable. The dot variable has an 'has_errors' property which lets you know if the method succeeded or not. If not, then the errors are returned in an array called 'errors' which is a sub-property of the dot variable.

The dot variable that is returned by the .change_record() or .enter_record() method is actually an object (the 'validation' object), and it has a .Format() method, which allows you to format the error message into a friendly text or html format ready for display to the user. The format() method can use these placeholders:

$(field) Placeholder for the fieldname for which the error occurred
$(error) Placeholder for the plain-text error message
$(errorhtml) Placeholder for the HTML formatted error message

 

In the above examples, you can see that we call the .Format() method with this argument:

"$(field) = $(error)"+crlf()

The crlf() is necessary because the .Format() method does not put in line breaks between each message.

The string "$(field) = $(error)" contains two placeholders which get replaced by each field for which an error occurred and by the corresponding error message.

Validating Data Before Committing It

In the above examples, the validation occurred at the time we actually tried to enter the record, or save the changes. However, the <tbl> object also has a new .validate_record() method that allows you to validate the record against the Field Rules when you are using the older <tbl>.enter_records() and <tbl>.change_records() methods.

<tbl>.validate_record() returns a default formatted error string if there are any errors.

You can pass in an optional format string to the .validate_record() method to control the formatting of the error message. The syntax for the format string is the same as the .Format() method of the validation object, described above.

Example (assuming the same field rules are defined as in the above example)

 

DIM t as p

t = table.open("customer")

t.enter_begin()

t.company = "XYZ Compay"

?t.validate_record()

<tr><td>customer->LASTNAME</td><td>Field is required: LASTNAME</td></tr>

 

Notice that if you do not pass in a format string to the .validate_record() method, it returns an HTML table with the error messages.

 

Posting Rules Now Honored During Xbasic Data Entry

The following Interactive Window session (for AlphaSports) shows that Posting Field rules are now honored when data entry is performed using Xbasic. Previously, they were only honored when doing edits using the user interface.

 

pt = table.open("product")
? pt.PRODUCT_ID
= "P001 "
? pt.QTY_IN_STO
= 140

pi = table.open("invoice_items")
pi.enter_begin(.t.)
pi.INVOICE_NUMBER = "1001"
pi.PRODUCT_ID = pt.PRODUCT_ID
pi.QUANTITY = 10
pi.enter_end(.t.)

? pt.QTY_IN_STO
= 130

pt.close()
pi.close()


 

 

Miscellaneous Xbasic Array Improvements

Implicit Array Declaration

When you use the [] syntax to set a property array, you do not need to have dimmed an array in advance. For example, as shown in this Interactive window session:

b[] = "alpha"
b[] = "beta"
?b.size()
= 2

 

This example uses a property array.

a.array[].name = "Alpha"

a.array[..].city = "Burlington"

a.array[].name = "Microsoft"

a.array[..].city = "Seattle"

?a.array.size()

= 2

?a.array.dump_properties("Name|city")

= Alpha|Burlington
Microsoft|Seattle
 

 

Dynamic Array Sizing

When you delete entries from an array, the array size does not change. With dynamic array sizing turned on, deleting an entry from an array will resize the array. The following Interactive window session demonstrates:

 

dim array[3] as c

array[1] = "alpha"

array[2] = "beta"

array[3] = "gamma"

?array.size()

=3

array.delete(2,1)

?array.dump()

= alpha
gamma
?array.size()

=3
 

Now, repeating this exercise, but turning on the array's 'dynamic' property:

 

dim array[3] as c

array.set_dynamic(.t.)

array[1] = "alpha"

array[2] = "beta"

array[3] = "gamma"

?array.size()

=3

array.delete(2,1)

?array.dump()

= alpha
gamma
?array.size()

=2

 

The <array>.set_dynamic(flag) method can be used to turn on/off the dynamic sizing property of an array. The <array>.get_dynamic() method can be used to determine the state of an array's dynamic sizing property.

 

If you dim an array with a size of 0 (and then populate the array using the [] syntax), the dynamic sizing property is automatically assumed to be set to True. For example:

dim array[0] as p
array[] = "alpha"
array[] = "beta"
?array.size()
=2
array.delete(2,1)
?array.size()
=1


'This shows that an array dimmed with an initial size of 0 defaults to dynamic properties on, whereas
'an array dimmed with non-zero size defaults to dynamic properties off.
dim arr1[0] as p
dim arr2[1] as p
? arr1.get_dynamic()
= .T.
? arr2.get_dynamic()
= .F.
 

Appending Entries to An Array

The new <array>.append() method makes it easier to add new entries to an array. <array>.append() adds a new entry to the array and returns the index of the new array slot. For example:


dim pArrray[0] as p
?pArray.size() =
0
dim index as n
index = pArray.append()
?index
=1
pArray[index].name = "Sam"
pArray[index].city = "Boston"

 

Initialize Array From Table

The <array>.initialize_from_table() method now has two new parameters that allow you to optionally append to an existing array (instead of always initializing it), and to specify a list of fieldnames (rather than populating the array with every field in the source table).

Also, it is no longer necessary to dim the array with the correct size before you execute the .initialize_from_table() method - you can dim it as a 0 size array and the array will be resized correctly based on the number of records that satisfy your filter criteria.

For example, here is an Interactive window session (in the sample Alphasports database):

'Start with empty array (automatically dynamic)
dim arr[0] as p
'Pull in NY records from Customer table...
arr.initialize_from_table("customer","bill_state_region='NY'","",.f.)
?arr.size()
= 8


'Now, pull in MA records from the Customer table. Notice that the append flag is set to TRUE so that records are added to the end of the array.
arr.initialize_from_table("customer","bill_state_region='MA'","",.t.)
? arr.size()
= 29
 

In this example, only selected columns are loaded into the array.


dim fieldList as c
fieldList = <<%txt%
CUSTOMER_ID
COMPANY
FIRSTNAME
LASTNAME
%txt

dim arrlim[0] as p
arrlim.initialize_from_table("customer","bill_state_region='NY'","",.f.,fieldlist)
? arrlim[2]
= COMPANY = "Alpha Software New York"
CUSTOMER_ID = "00000010"
FIRSTNAME = "Bryan"
LASTNAME = "Bloomberg"

 

Session Folders

The new a5.Get_Session_Path(sessionNumber) method creates a session folder, (or if the folder already exists, returns the name of the session folder). When the session is closed the session folder is automatically deleted. Creates or returns existing a session folder on demand (for session) - when the session gets closed, the folder is automatically cleaned up:
 

For example, assume that you have form with a button on it. The button has this Xbasic code:


 

spath = a5.Get_Session_Path(topparent.SessionHandle())

'save a file to the session folder

file.from_string(spath+"\fake.txt","This is a test")

'store the name of the file just created on the clipboard

clipboard.set_data(spath + "\fake.txt")

 

Now, without closing the Form with the button down, go to the Interactive window:

fn = clipboard.get_data()

?file.to_string(fn)


The contents of the file is shown.

Now, close the Form with the button. And go back to the Interactive window and execute this command again:

?file.to_string(fn)

 

You get an error because the file no longer exists - when the session was closed, the session folder was removed.

Util::PerformanceTimer Object

 The Util::PerformanceTimer object works the say way that the Util::Timer object works, but instead of reporting a single number, total elapsed time, it breaks down the elapsed time into CPU time, user time and kernel time (all in microseconds) For example:

Dim t as Util::PerformanceTimer
t.start()
'some code you want to time
t.Stop()

?t2.CPUTime
= 171601
?t2.ElapsedTime
= 7088085
?t2.KernelTime
= 62401
?t2.UserTime
= 109200

 

Stritran_multi_expressions() Function

The strtran_multi_expressions() (case-sensitive version) and stritran_multi_expressions() allow you to perform complex 'search and replace' operations in strings. These functions are extremely versatile because the replacement strings can be expressions that Alpha Five will evaluate when it does the replacement.

Syntax: C textOut = stritran_multi(C textIn, C searchReplaceString)

 

Where

textIn String in which you want to perform a search and replace operation.
searchReplaceString A CR-LF delimited list of search and replace values. Each row in the list has this format:

searchString=replacementExpression

 

If a  replacementExpression contains any search strings, you can force the replacement to occur recursively by using a double = sign. e.g.

searchString==replacementExpression

 

Each = sign causes an additional recursion. For example:

searchString===replacementExpression

 

will recurse over the data 3 times.

 

 

Example

dim string as c

string = "Hello {name}. Today is {date}."

dim sr as c

sr = <<%txt%

{name}="Peter"

{date}=date()

%txt%

?stritran_multi_expressions(string,sr)

= Hello Peter. Today is 8/26/2009

 

'Now try recursive replacement

dim string as c

string = "Hello {name}. Today is {date}."

dim sr as c

sr = <<%txt%

{lastname}=="Smith"

{name}="Peter {lastname}"

{date}=date()

%txt%

?stritran_multi_expressions(string,sr)

= Hello Peter Smith. Today is 8/26/2009

 

 

.Data() Method On DBF Tables and Property Variables

When using AlphaDAO, the method used to read data from a resultset is <resultset>.data("columnname")

In order to make it easier to write Xbasic code that will work the same when iterating over a DBF table, or a SQL resultset, or a property variable, the .data() method is now supported for both DBF tables and property variables.

For example:

t = table.open("customer")

?t.lastname

= "Smith                      "

?t.data("lastname")

= "Smith"

 

Notice that the <tbl>.data() method trims trailing blanks on character fields.

dim p as p

p.firstname = "Frank"

?p.data("firstname")

= "Frank"

 

*Flatten_Interfaces() Function - Combines Two Interfaces into a Single Interface

Allows you to combine two objects created from different classes into a single new object that has the methods of each individual object.

The following example demonstrates the function.

For example, assume you have defined the following two Xbasic classes:

 

define class global myclass1

  function hello as c()

      ui_msg_box("Hello","Hello world")

  end function

end class

 

define class myclass2

  function bye as c()

      ui_msg_box("Bye","Goodbye world")

  end function

end class

 

Now, from the Interactive window:

dim my1 as myclass1

my1.hello() 'brings  up the Hello dialog

my1.bye() 'causes an error because this object does not have a .bye() method

dim my2 as myclass2

my1.bye() 'brings up the Bye dialog

dim my3 as p

my3  = *flatten_interfaces(my1,my2)

my3.hello() 'brings up the Hello dialog

my3.bye() 'brings up the Bye dialog

 

As the example demonstrates, the new my3 object is a composite of my1 and my2 and it has both a .hello() method and a .bye() method.

In effect, the *flatten_interfaces() function allows Xbasic to mimic C++'s multiple inheritance feature.

Note: You can also use *flatten_interfaces() with objects based on Alpha Five's system classes.

For example, here we use the table object as one of the inputs to *flatten_interfaces()

 

t = table.open("customer")

dim mc1 as myclass1

dim both as p

'combine our own class with the table instance

both = *FLATTEN_INTERFACES(mc1,t)
 

both.hello() 'brings up the Hello dialog

?both.customer_id

= "0000001"

?both.recno()

= 1

*property_difference() Enhancements

The *property_difference() function now honors comparison of elements in a collection (Xbasic variable of type 'U').

The *property_difference() function takes two dot variables and compares them. Any differences are stored in a result dot variables.

For example, the following Interactive window session shows two dot variables, 'pp1' and 'pp2'. Note that both dot variables have the same value in their 'name' property, but different values in their 'city' property. Also, pp1 has a 'state' property and pp2 does not.

 

dim pp1 as p
pp1.name = "alpha"
pp1.city = "boston"

pp1.state = "ma"

dim pp2 as p
pp2.name = "alpha"
pp2.city = "ny"
 

'Get the difference between pp1 and pp2 and store result in pp3
delete pp3
dim pp3 as p
*property_difference(pp1,pp2,pp3)

 

'Examine pp3 - it shows the properties that have differences - 'city' and 'state'
?pp3
= city = "boston"

state = "ma"

 


Now, add a collection to pp1 and pp2
dim pp1.col as u
pp1.col.set("key1","value of key1")
pp1.col.set("key2","value of key2")

dim pp2.col as u
pp2.col.set("key1","modified value of key1")
pp2.col.set("key2","value of key2")

'Find the difference between pp1 and pp2 and store result in pp3
*property_difference(pp1,pp2,pp3)

 

'Examine pp3 - notice that it now reports that the collection (pp3.col) contains differences.
?pp3
= city = "boston"

state = "ma"
+col.
 

'Dump the contents of the collection - notice that the collection only has an entry for the key that was different.
?pp3.col.dump("key=value")
= key1=value of key1