MoneyWorks Manual
The MWScript Language
The MWScript expression syntax is compatible with the existing MoneyWorks expressions that you use in custom forms and reports. MWScript simply adds handlers and statements. Flow control is closely analogous to that provided by the report writer.
Normally a newline marks the end of a statement. So one statement per line. The only exception to this is that string constants (as of v9.1.8) may contain newlines which will result in a statement continuing over multiple lines,
Types
MWScript supports the same types as are available in the report writer. As in the report writer, the language is very weakly typed (values are freely type-promoted as necessary).
Type Example
- Number
5.35
- Date
'1/1/13'
— note that single quotes are for dates, not text strings - Text strings
"mwscript"
or`also a string
`. You can embed one kind of string delimiter inside a string that begins and ends with the other kind of delimiter`string with " inside it
`. You can also escape a delimiter using a backslash. You can also include newlines and tabs using backslash escape codes"\t\n\""
.
Finally, as of v9.1.8 Nowdoc-style multiline string constants may also be used. A nowdoc string begins with a delimiter that you define using <<DELIM
and then ends with the delimiter you defined on a line by itself: DELIM
. You can also have an inline Nowdoc string where the closing delimiter is on the same line as the start. In this case, the initial delimiter definition ends with a space, which is not part of the string. e,g: <<NOWDOC inline dowdoc stringNOWDOC
- Selection — create via CreateSelection(tableName, search) function.
- Table — create via CreateTable() function.
- Record — created by looping over a selection with foreach
- Associative Array — create with the CreateArray() function
Associative arrays use the syntax arrayname[key], where key can be any text up to 31 characters, an integer, or a date (internally, integers and dates are represented as text formatted so that lexical sort order matches numeric/date sort order). Data values can be any other type.
let myArray = CreateArray() let myArray[0] = "thing" let myArray["mykey"] = myArray[0] let myArray['31/1/12'] = 500
Arrays, like Tables, are always passed by reference, so if you assign an array to another variable, or pass it as a parameter to a function, you are not copying the array. If you want to copy an array, you must allocate a new array with CreateArray and explictly copy all of its members.
- Handle — some objects are referenced by an opaque handle. This includes window and list handles that MoneyWorks might pass to your script, and also File handles, JSON parser handles, XML parser handles etc that are returned by creation functions. Where noted in the documentation for a creation funciton, you may need to explicitly free such handles when you are finished with them. You do not need to explicitly free tables, arrays, or selection.
Properties
Properties are variables that last for the lifetime of the script. They are instantiated each time the script is loaded and discarded on unload.
Properties are defined outside of message handlers.
property mytextglobal = "qwertyuiopasdfghjklzxcvbnm"
If you need a property to be persistent across sessions, you can store the value in the database (the User2 table is appropriate for this, see the SetPersistent and GetPersistent functions). Load the value in the script's Load handler (or lazily load the first time it is needed), and store it in the Unload handler if it changed. Keep in mind that every user will be executing the script with their own copy of the properties, so you may need to take steps to stop one user from clobbering values stored by another user, or you might use the user's login initials as part of the storage key to store per-user settings.
You can declare a simple-type property to be persistent. If you do this, MoneyWorks will automatically save and load its value (locally, on each client) when your script is Loaded and Unloaded.
persistent property MyPrefValue = "foo"
Note that these persistent values are stored per client machine and all scripts for all documents the client might open or connect to are in the same namespace. Once the script has been run once on a particular computer, keep in mind that the value of the property at load can be different from the value that it appears to be initialised to in the script. To store persistent values per-document, use the SetPersistent and GetPersistent functions.
Constants
Constants are named values for use within the script.
Constants are defined outside of message handlers.
IMPORTANT: Every script should declare a constant with the name meta
, which will be used when reporting errors the script might generate. The string should therefore provide a way for them to contact you.
constant meta = "Script for Something by Your Name and your URL"
The meta contant will be used by MoneyWorks to identify your script to the user. It must be a string.
Handlers
A handler defines a callable function with optional named parameters and an optional return value. A handler is a peer of the built-in intrinsic functions and can be used in expressions in the same way.
A handler is defined by the keyword on
, followed by the handler name and an optional list of parameter names. The handler body ends with the keyword end
on a line by itself.
MoneyWorks will automatically call some specially-named handlers automatically to allow you to intercept various UI functions. All you need to do is include the handler in your active script, and MoneyWorks will call it (see the next section, Standard Handlers).
If a parameter required by your handler is not supplied by the caller, then the value will be NULL. Parameters are untyped, so if callers don't provide a value of the correct type, you should convert the type yourself to the required type (usually using TextToNum or NumToText)
Comments
Comments use the C++ form. //
for a to-end-of-line comment; /*
and */
to begin and end a block comment that can span lines or within a line.
Assignment
The let
statement assigns the result of an expression to a new or existing variable. If the variable has not previously been seen in the handler and is not a property, then it is implicitly declared as a local variable in the handler.
let myVar = Today() + 7 // adding a number to a date adds that many days
Conditionals
Conditional flow control is done with if
, elseif
, else
, and endif
. Zero or more elseif
clauses are allowed for an if
, followed by zero or one else
clause, followed by an endif
.
Short form:
if condition // do something endif
And the long form:
if condition // do something elseif anothercondition // can have any number of these // do something else else // up to one of these // do other thing endif
The long form effectively provides switch/case functionality.
While loops
General looping can do done with a while loop. Loops can make use of the break
and continue
statements for early exit or short circuit (just as in C-like languages, and the report writer).
while condition if special_condition break // exit the loop endif if another_condition continue // go back to top of loop, skipping do stuff endif // do stuff endwhile
For loops
There are several kinds of for loops that operate on different kinds of collections or ranges. Each kind starts with the keyword foreach
and ends with endfor
.
The general form is
foreach loop-control-var in type-keyword expression
Foreach declares a local loop control variable whose scope is limited to the loop body. Type keywords can be:
- database table names (defining a loop over a selection of records). Available table names are:
account
,ledger
,general
,department
,link
,transaction
,detail
,log
,taxrate
,message
,name
,payments
,product
(items),job
,build
,jobsheet
,bankrecs
,autosplit
,memo
,user
,user2
,offledger
,filter
,stickies
,lists
,login
,contacts
,inventory
,assetcat
andasset
. The expression must yield a selection variable for the specified table. The loop control variable is a record, whose fields can be accessed by suffixing the variable name by a field name (e.g. rec.ourref). The variable used on its own will yield a 1-based index. - the keyword text which will iterate over words or lines in comma or newline delimited text. The delimiter is determined automatically—if there is at least one newline, then iteration is by line, otherwise iteration is by comma. Every line of multiline text should terminate with a newline (including the last one!).
- the keyword textfile that will iterate over lines in a textfile loaded from the local filesystem using the "file://" scheme, or from an HTTP server using the "http://" or "https://" scheme. You can use the http scheme to access data from remote data sources (e.g. REST servers. It retrieves data using the GET operation only). If the path is supplied, it must be in the temp directory, the custom plugins directory, MoneyWorks app support directory, or have a file extension of .txt or .csv. If the path is specified by the user, it can be anywhere or have any extension (but obviously should be a text file). Anywhere outside these locations will need to be specified in a file open dialog box by the user (which will be automatically invoked for any invalid or empty path).
- the keyword array which will iterate over key values in an associative array variable;
- and finally no keyword but rather a parenthesis-enclosed pair of values defines a simple numeric range (with an optional step value). The values can be full expressions but must be numeric. If the finish value is less than the start value, no iterations will occur (unless the step is negative).
Foreach variants:
foreach rec in transaction CreateSelection("transaction", "status=`P`") foreach rec in account someSelectionICreatedEarlier foreach word in text "foo, bar, baz" foreach line in textfile "file://" foreach line in textfile "http://" foreach key in array myArrayVar foreach i in (0, 10) // integers foreach i in (100, 0, -10) // integers with optional step
Range Example:
// log the numbers 1...100 foreach i in (1, 100) syslog(i) endfor // note that (100, 1) will iterate 0 times, unless you supply a negative step // log the numbers 100, 90, 80, ... 0 foreach i in (100, 0, -10) syslog(i) end for
Array Example:
// foreach in array iterates over keys; get the value by subscripting with the key foreach key in array anArray syslog(key + " = " + anArray[key]) // key is always text end for
List Example:
// comma-delimited text foreach w in text "foo, bar, baz" syslog(w) endfor // newline-delimited text foreach line in text "firsttlinensecondtlinenlasttlinen" syslog(line) end for
Selection Example:
// loop control is of record type; use dot-notation to access fields // the naked loop contol variable is a 1-based index foreach a in account CreateSelection("account", "code=`1@`") syslog("record #" + a + ": " + a.code + " " + a.description) end for
Running Scripts
You can install multiple scripts in a document and enable or disable them individually.
Use the Show Scripts command to show and edit the scripts installed in a document. Note that only one user at a time can make changes to scripts.
You can add, delete, and rename scripts (use the icons at the bottom of the sidebar).
Active scripts are loaded at login and unloaded at logout, and a script is also unloaded/reloaded when you click the Activate button in the script editor toolbar (the old version of the script is first unloaded if it was loaded, then the modified script is compiled and loaded).
You can keep inactive scripts in a document. They won't do anything until you activate them.
You can use an inactive script to store boilerplate text for other purposes (such as a mail template, html, json etc). See GetScriptText. As of v9.1.8 you can also store such boilerplate text conveniently within your script using a Nowdoc string.
Stopping problematic scripts
If you write a script that gets into an endless loop, you should be able to stop it using the Stop button in the script progress window that will appear when the script is busy for more than a few seconds. The Stop button is available for any user who has the Scripting privilege. This will not deactivate the script; it will just terminate execution of the current handler.
If you write a script that gets into an endless loop putting up alert windows, you will not get a progress window Stop button. If a script presents an alert, any user with Scripting privileges can stop the script by holding down the ctrl+shift keys when clicking the alert's button. You will get a further alert asking if you want to disable the script. Note that if you disable (deactivate) a script, be sure to reactivate it before closing the script window because otherwise it will be deactivated for all users.
If you write a script that causes problems in its Load handler, thereby preventing you (or anyone) from logging in, you can suppress the loading of document scripts by holding down Option+Shift (Mac) / Ctrl+Shift (Windows) when logging in. Note that this option is available only for users who have the Scripting privilege.