Edit

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 and asset. 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.