Scripts are use to add custom logic to an app. This can for example be used to define what will happen when a user presses a button in a user interface or to perform a certain operation every time a record of a certain model is created. Adding scripts to your app is very easy and can be done without knowing anything about programming. At the same time, if you know programming, you are free to write advanced scripts to perform complex logic in your app without hindrance. The three different methods for creating script can be seen as a hierarchy where QuickActions are the quickest and easiest way to perform the most common actions in an app. JavaScript is on the other end of the spectrum allowing you to create advanced actions with full control over what happens. Logic sits in the middle by still being easy to use but still allowing for full control over what the script does. This hierarchy is also visible by the fact that using a simple action also generates all the information needed for the more advanced script creation options. This allows one to use a QuickAction to perform a task and then switch over to using Logic or JavaScript and see how the same task could be performed using thoose methods. It is however not possible to create a script using JavaScript and then switch to Logic and see how it would look there.

Quick Actions

QuickActions are a quick and easy way to perform the most common operation for the context of the script. All QuickActions are created by Appivo and guaranteed to work properly. To use QuickActions you simply open up a script editor in the builder (for example editing the callback for a button in a userinterface) and then select that the script should be defined using QuickActions (the script editor should default to this mode). Once the script editor is set to use QuickActions you simply pick the action
that you wish to perform from the dropdown and then input any parameters needed for the QuickAction.

Logic

Logic allows you to define app logic, more powerful than QuickActions, without writing any code. Resembling a puzzle, the pieces or blocks connect only in ways that make sense. The blocks are parsed and app code is generated.
The blocks are parsed from top to bottom, and have connections above and below that let you place them in the order you want them to be run. Some blocks also have connections to the right. Blocks connected on these right-side connections are called “arguments” and are used to configure or modify the behavior of a block. For example, a “Get user name” block may take a user block as an argument to specify which user’s name it should output. Figuring out which arguments fit in which block may be difficult. Luckily some help is available through tooltips. Just hold the mouse cursor over the colored part of a block and the tooltip will appear. The tooltip will usually have a short description, detailing the blocks purpose, along with information on the argument inputs and output value.

Blocks can be divided into a few notable types:

  • Instruction blocks represent instructions – for example, a “send mail” block will send an email.
  • Value blocks represent values such as numbers and text strings (like “hello”, for instance). Value blocks are often used as arguments to configure instruction blocks.

Note: Some blocks are both an instruction and a value. In these cases, the instruction normally comes down to finding the value that the block should represent. Arguments can be used to alter the value. For example, a “get name” block may have different values depending on which user is provided as the argument.

  • Expression blocks perform logical and mathematical on their arguments, such as addition, substraction, or resolving whether a statement is true or not.
  • Control flow blocks controls how your puzzle is parsed. These usually take an expression as the argument and directs the parsing on different paths depending on whether the expression is true or not. For example, you may want to display two different messages depending on if a user has a specific role or not, this could be performed with the “if” block.
  • Variable blocks are special blocks that can be used to temporary store values. For example, maybe you want to overwrite the value of a widget, but need its old value for some calculations later in the puzzle, by storing the old value in a variable block this is possible.

Javascript

Using JavaScript directly is the most advanced way to add custom logic to your app. It requires you to be familiar with programming and the syntax of JavaScript. Using JavaScript you have full access to all the APIs available in the platform and can create very advanced logic for your app. However if you are not used to writing code it is very easy to make mistakes that will cause the script to malfunction. Therefore it is recommended to use QuickActions and Logic for creating scripts until you feel comfortable writing code.

Client Side Scripting

Client-side scripts run in the web-browser or on the mobile-device when the user interacts with your application. You can find API-documentation for client-side scripting over here. Here are some examples.

Getting the value of a widget

var widget = context.getWidget("MyTextField");
var name = widget.getValue();

Setting the value of a widget

var widget = context.getWidget("MyTextField");
var name = widget.setValue("Bill Joy");

Submitting a form (including contents of child-forms)

var form = context.getForm("CustomerForm");
form.submit({
   full: true
});

Creating a record from a form

Sometimes it is useful to create records from forms without submitting them. Providing the “full”-option will ensure that related records are included, if any, from child-forms and/or widgets holding related data (such as DataGrids etc).

var form = context.getForm("CustomerForm");
var record = form.createRecord({
   full: true
});

Opening another view for showing a record

Example: On the first view you have references to records, perhaps in a DataGrid, and when clicking on a representation of the record (perhaps a row in the DataGrid), you want to open another view with a form holding all the fields of that record.

context.setView("CustomerView", record.id);

Publishing a message to a topic

var bus = context.getMessageBus();
bus.publish("#MyTopic", {
   Name: "John Doe",
   Age: 37
});

Listening to messages sent out on a specific topic

var bus = context.getMessageBus();
bus.subscribe("#MyTopic", function(message) {
   context.log("Received a message " + message);
});

Updating an HTML widget using a query

Sometimes an HTML widget is used to render data in a custom fashion. The HTML widget’s content is Handlebars template, so it can reference data. In this example we have an HTML widget, called MyHTML which needs data from a query as input.

// Get the widget
var html = context.getWidget("MyHTML");
// Get our query-handle
var qh = context.getQueryHandle("MyQuery");

// We just want the first record
qh.setLimit(1);

// Execute the query
qh.execute({
  onSuccess : function(result) {
     var res = result.getRecords();

     // We know we just requested a single record
     var rec = res[0];

     // Refresh the html-widget with our record as input-data
     html.refresh(rec);
  }
});

In mobile user-interfaces you can also use Javascript to make use of plugins for advanced functionality. Note that first of all you have to add the required plugins to your application. These plugins are standard Cordova plugins. Appivo has chosen to expose a selection of high quality plugins, if there is a plugin you feel should be included feel free to request it. Each plugin exposes its own API which you can use.

Barcode Scanner

The most basic example, just scanning a code with default settings.

var scanner = context.getNativeAPI("barcodeScanner");
scanner.scan(function(result) {
       alert("Scanned " + result.text);
   },
   function(error) {
       alert("Scanner failed " + error);
   }
);

Perform a scan with some options specified.

var scanner = context.getNativeAPI("Barcodescanner");
scanner.scan(function(result) {
     context.alert("Scanned " + result.text);
  }, function(error) {
     context.alert("Failed to scan: " + error);
  },
  {
     preferFrontCamera: true,
     formats: "QRCODE,PDF_417",
     orientation: "portrait"
});

Server Side Scripting

Rules and Actions are executed on the server-side, this part of the documentation describes the Javascript API for programming ScriptActions. You can see the detailed API documentation over here.

Examples

Working with the database


Using transactions

Using transactions when working with the database is essential. This allows you to perform a set of data-operations in an atomic fashion.

// Initiate a transaction
context.begin();

context.create(record1);
context.update(record2);

// Commit our transaction
context.commit();

Creating a record

The database-operations all require Record-instances as input.Often one needs to create a record from data that has been computed or retrieved from other sources.

// Create a Record out of a map of key/value-pars
var record = context.createRecord("MyModel", {
  "Name": "James Gosling",
  "Age": 65
});

// Write it to the database
context.create(record);

Executing a Query and iterate over its result

There are several ways of executing database queries (and also a couple of ways to define them). Here is an example showing how to execute a query that has been defined in the builder named “MyQuery”. The query-method returns a Result which allows you to fetch records one by one.

var result = context.query("MyQuery", {
  "MyParam": 7
});
try {
  while(result.hasNext()) {
    var record = result.next();
    context.log("Got record with ID: " + record.getId());
  }
} finally {
  result.close();
}

Execute a query and get the result as an array

You may also use the queryForArray-method which will automatically iterate over the Result and build an array, this should only be done when the Result is known to be fairly small as the platform will disallow very large results to be retrieved this way.

var records = context.queryForArray("MyOtherQuery", {
   "Param": "Sweden"
});

for (var i = 0; i < records.length; i++) {
   context.log("Got record with ID: " + record.getId());
}

Sending a simple HTTP GET-request

Sending HTTP-requests to remote systems can be a simple way of integrating. Here’s an example of how to perform a simple GET-request, fetching data in JSON-form. The platform makes it very easy to communicate with REST-APIs using the JSON- and XML-dataformats. When working with HTTP it’s good to know that the platform will assume JSON to be the preferred data-format unless a content-type is specified.

context.http({
   "url": "https://someotherserver.com/api/todos"
}).then(function(response) {
   // The response received here is the parsed JSON-document, a javascript-object.
   // The JSON looks like this: {"todos": [ {"task": "Call mom", "deadline": "2020-06-15"}, ...]}
   context.log("Received " + response.todos.length + " todos");
});

A basic HTTP-POST request

Of course you may also at times want to post data to remote systems. That’s about as easy as fetching data, especially if the data-format is JSON.

// Here's some data we want to post to a remote system
var data = {
  "country": "Sweden",
  "population": 10100000
};

context.http({
   "url": "https://someserver.com/api/v1/countries",
   "method": "POST",
   "body": data
}).then(function(response) {
   context.log("Success!");
});

Uploading a file to a remote system

Uploading files is not much harder. Here’s an example showing how to post a document-record to a remote system. This is an example of a script connected to a data-rule. Note how we do not need to specify a content-type, the platform will automatically set the content-type of the request to whatever the document-record has been stored as.

// Get the record that is in scope for this script
var record = context.getRecord();

// Our record has called Photo which links to a DocumentRecord for storing pictures
var document = record.getRelatedRecord("Photo");

context.http({
    "url": "https://somepicturedatabase.com/upload",
    "method": "POST",
    "body": document
});


A multipart POST-request

The platform can handle complex multipart-type requests of all variants. Here’s an example showing how to send a multipart/mixed type POST-request. This example also shows how you can specify content-type and headers. The script here is assumed to be connected to a webhook-rule (as we are fetching a file-id from a request-parameter). Note that for multipart-type request the body provided should be an array of body-parts (where each can have their own content-types and headers).

var fileId = context.getRequest().getParameter("file");

// Get an access-token from our application-configuration
var accessToken = context.getConfigParam("AccessToken");

context.http({
        url: "https://somesystem.com/api/v1/attachments",
        method: 'POST',
        contentType: 'multipart/mixed',
        headers: {
             "Authentication": accessToken,
             'Accept': 'application/json'
        },
        body: [
            {
                contentType: 'application/json',
                body: {
                    "fileName": "shuttle.jpg",
                    "resourceName": "shuttle.jpg",
                    "description": "Spaceshuttle taking off from Cape Canaveral"
                }
            },
            {
                contentType: 'image/jpg',
                headers: {
                    'Content-Disposition': '*; filename="image.jpg";'
                },
                body: {
                    "file": fileId,
                    "filename": "shuttle.jpg"
                }
            }
        ]
});

Handle a ServiceRequest and interact with an external HTTP-service

To build custom REST-API endpoints in your app it’s usually handy to bind a webhook-rule to a script-action. Here’s how to handle such a request from a script and

// Get the ServiceRequest / ServiceResponse pair
var sreq = context.getRequest();
var sres = context.getResponse();

// Read the body of the ServiceRequest
var options = sreq.readObject();

// Get a value of the JSON-object from the ServiceRequest
var max = options.max;

// Enter asynchronous handling mode - we can now respond to this request at a later time, after this script has exited
sreq.startAsync();

// Issue an outbound request to a remote service
context.sendHttpRequest({
   "method": "POST",
   "url": "https://foo.bar.com/service",
   "headers": {
      "content-type": "json",
   },
   "body": {
      "threshold": max
   }
}).then(function(response) {
    // Read a JSON-object from the response
    var data = response.getContentAsObject();
    
    sres.setStatus(200);
    sres.write({
       "count": data.count
    });
}, function(error) {
    // Our request failed, we will fail the service-invocation too
    sres.sendError(500, error);
});