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 much about programming. However, if you know programming, you are free to write advanced scripts to perform complex logic in your app. 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 at the other end of the spectrum allowing you to create advanced actions with full control over what happens. Logic sits in the middle by 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 fast and easy way to perform the most common operations for the context of the script. All QuickActions are created by Appivo and guaranteed to work properly – you can think of them as macros. 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 custom 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 distinct 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 native 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

Here are couple of examples using the barcode-scanner plugin. 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("Person", {
  "Name": "James Gosling",
  "Age": 65
});

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

Note that when creating a record you can actually create an entire graph of records all in one go if the records model has relationships. If we assume that our Person model above has a relationship called Child, which points to another Person-record, we could create a Person and his/her children in one go like this:

// Create a Record out of a map of key/value-pars
var record = context.createRecord("Person", {
  "Name": "Adam Smith",
  "Age": 65,
  "Child": [
      {
          "Name": "Joseph Smith",
          "Age": 22
      },
      {
          "Name": "Bethany Smith",
          "Age": 18
       }
  ]
});

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

Creating a related record

Often times you may want to create a record that is related to one or several other records. This can be done in a number of ways – one is to populate the attribute representing the identity of the related record.

Example:

Assume we have two models Order and Article where an Order is always related to an Article. The relationship between the two have been modeled and the relation ends are using the default names (which will be the same as the connected models, the user can alter this in the model-editor however) – then the Order-model will have an attribute called Article_id.

var article = < fetch article >;

var order = context.createRecord("Order", {
    "OrderNumber": 1234,
    "Amount": 3,
    "Article_id": article.getId()
});

context.create(order);

Another way to establish the relation ship is by populating the relation itself, like this:

var article = < fetch article >;

var order = context.createRecord("Order", {
    "OrderNumber": 1234,
    "Amount": 3,
    "Article": article
});

context.create(order);

This has the side-effect that the article which is now fully attached will be updated, this may or may not be wanted – but there is a way around that – a record can be turned into something called a reference record, a reference record is a record that only holds the identity of a record , no other data, and by using reference records one can avoid unwanted updates to related records. Here’s an example of how this could be used:

var article = < fetch article >;

var order = context.createRecord("Order", {
    "OrderNumber": 1234,
    "Amount": 3,
    "Article": article.toReference()
});

context.create(order);

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.http({
   "method": "POST",
   "url": "https://foo.bar.com/service",
   "headers": {
      "content-type": "json",
   },
   "body": {
      "threshold": max
   }
}).then(function(response) {
    try {
      sres.write({
         "count": response.count
      });
   } finally {
     // We must explicitly close the request
     sres.close();
   }
}, function(error) {
    // Our request failed, we will fail the service-invocation too
    sres.sendError(500, error);
});