JavaScript in Actioner
Explore how to build components and design apps with JavaScript
You can use action components as building blocks and design interactions between them with JavaScript.
Data in Actioner can be referenced and transformed with JavaScript. You can write JavaScript inside curlies {{ }}
on any field indicated with { }
.
On these fields, you can also reference to the components of your action. You can use components as building blocks and design interactions between them with JavaScript.
Accessing input and request values
The value of an input can be the value entered or selected by users while running the action. The values of inputs can also be set from the To access the inputted value (the value entered or selected by users while running the action) of an input, use below:
{{input.my_input}}
To access the response of an API request, use below:
{{request.myRequest.response}}
This being said, JavaScript in between {{ }}
is limited in a few ways. To get a value from {{ }}
, it needs to be a self executing function, method, or single value such as:
{{input.my_input.toLowerCase()}}
or {{request.myRequest.response.body.map(row=>row.id)}}
Viewing input and request data
We’re going to take a look at how you can view your data before getting into accessing nested data structures. Viewing your data gives you a birds-eye view of the maze to the nested data, and therefore, makes it easier to access the values you’re looking for.
The data of an input can be accessed via {{input.my_input}}
. As you type between {{ }}
, smart autocomplete suggests from available inputs. You can type input
or hover over to see the list of your inputs and their data.
Similarly, the data of a response of an API request can be accessed via {{request.myRequest.response}}. As you type between {{ }}, smart autocomplete suggests from available requests and their fields. You can type request or hover over to see the list of your requests and their data.
In the below example, {{request.ListTickets.response.body.tickets}}
is a nested object. You can preview the raw data as you type and when you hover over it.
Getting data from an API request
Once you successfully send an API request, you’ll get data back and into Actioner. To get your request working and view its data, you can run your request or test your action.
Running a request
Open your action in design mode and find your request on Requests tab. Then click green Run button. You can see the response when you scroll down the page.
If your request requires values from other components, make sura that they work before running your request.
Testing your action
Testing is especially helpful if your request is including an input value (or even including another request that includes an input value). Testing the action is necessary for these cases since your request is requiring a value of an input or another request component.
Let's now take a closer look at the nested JSONs and how to access them.
Working with nested JSON
Nested JSON is a JSON file with its values being other JSON objects. They provide higher clarity by decoupling objects into different layers, making it easier to maintain.
With the help of Javascript, you can access data in an object in an array in an object. It is referred to as nested data, which is how Actioner gets values from input or request components.
While working with JSON data of API responses, there are two ways to access a nested object, through dot notation or bracket notation.
Dot notation is simply typing a dot . at the end of an object inside {{}}
and accessing the next object or property, such as
{{request.myRequest.response.body.object.key}}
Bracket notation is similarly typing a bracket [
at the end of an object inside {{}}
and accessing the next object or property, such as
{{request.myRequest.response.body.object['key']}}
Actioner provides smart autocomplete to access components and their fields while designing actions. When you have a JSON object and aren’t sure which fields, properties or methods you can access, go with the dot notation. In this case, dot notation is better than bracket notation since a [ will not bring up the autocomplete menu.
Accessing data in an object
Values in an object can be accessed using either dot notation {{object.key}}
or bracket notation {{object['key']}}
.
Property identifies can only be alphanumeric (and _ and $) when working with dot notation. Dot notation can be limiting (properties can’t start with a number), but adding a .
after an object shows a helpful autocomplete menu.
Below is an example response to myRequest
:
{
"tags":["user-management","payment-issues"],
"priority":"high"
}
{{request.myRequest.response.body['tags']}} // returns ["user-management", "payment-issues"]
{{request.myRequest.response.body.tags}} // returns ["user-management", "payment-issues"]
Accessing data in an array
Values in an array can be accessed using the index, or numeric location, of the target. The index always starts at 0, so the first element of an array can be accessed by using array[0]
.
Below is an example response to myRequest
:
{
"tags":["user-management","payment-issues"],
"priority":"high"
}
{{request.myRequest.response.body.tags[0]}}
// returns 'user-management'
Use cases for accessing and transforming data
Accessing and transforming input values
To insert input values into your requests and other inputs; use {{input.input_ID}}
. This returns the value entered when the action is executed (or tested).
If you've setup a text field named subject, to access its user-provided value during action execution; use {{input.subject}}
.
You can apply one-liner Javascript methods when you want to transform a user-provided input's value.
{{input.subject.toLowerCase()}}
// returns the subject in lowercase.
How to use input values in requests
- Request URL:
You can insert an input value on a URL path of an API request. The endpoint of the below request becomes /api/v2/tickets/{{input.ticketID}}
- Request params:
You can insert an input value on URL parameters of an API request. The value of the below query parameter becomes type:ticket organization_id:{{input.organization}}
- Request body:
You can insert input values in the body of your requests.
Accessing and transforming API responses
You can access API responses
- to retrieve specific data to be used as options in your input components.
- to replace values in your output components.
{{request.myRequest.response.body}}
returns the body of the response to myRequest. In most cases, the data you want to extract from an API response is nested inside a JSON object. Actioner supports dot and bracket notation to access nested JSON objects and arrays.
Let's say you built a request named ListProjects
. Below is the response to this request:
{
"projects":[
{
"id":211,
"name":"Development"
},
{
"id":212,
"name":"Operations"
}
]
}
Add .response.body
to the end of your request to access the JSON body of its response. In this example {{request.ListGroups.response.body}}
returns the above JSON. Accordingly, {{request.ListProjects.response.body.projects.[0].name}}
returns the name of the first element in projects array.
{{request.ListProjects.response.body.projects.[0].name}}
// returns Development
Mapping an API response array to a select component
If you want to get each project as an option in your select component, use below:
{{request.ListProjects.response.body.map(project=>project.name)}}
// returns [Development, Operations]
To get each project's id for a select component, use below:
{{request.ListProjects.response.body.map(project=>project.id)}}
// returns [211, 212]
It is possible to apply multiple JavaScript methods. If you want to lowercase the group names in this example, use below:
{{request.ListProjects.data.groups.map(group=>group.name).toLowerCase()}}
// returns [development, operations]
Accessing and transforming output sources
Table and chart sources
While configuring table and chart outputs, you need to define a source from a request. Typically, the output source is the request mapped to the Execution button.
Let's imagine you have an action that retrieves multiple objects in an array, such as a list of issues assigned to a user. ShowIssues is an API request that gets issues from your bug tracking tool and is configured to be executed when the action is run. The body of the response to ShowIssues is as below:
{
"issues":[
{
"id":219,
"via":{
"channel":"web"
},
"subject":"issue in main order flow",
"priority":"high",
"status":"open",
"tags":[
"order-system"
]
},
{
"id":220,
"via":{
"channel":"web"
},
"subject":"error while updating account",
"priority":"low",
"status":"pending",
"tags":[
"account-management"
]
}
]
}
If you add {{request.ShowIssues.response.body.issues}}
to the source of your table output, each issue will be shown in a row. If you add the same to the source of a chart output, it will become the dataset producing the graph.
Key value table sources
Let's imagine you are designing an action to retrieve a single issue through API and you want to show the issue's properties in a key-value table. GetDeal is an API request that returns a specific deal from your sales tool and is configured to be executed when the action is run. The body of the response to GetDeal is as below:
{
"deal":
{
"id":865,
"name":"Cloudhub deal",
"owner": 397213",
"amount":"$60,000",
"status":"appointmentScheduled"
}
}
You can add {{request.GetDeal.response.body.deal}}
in a key value table's source field to display each property in a new row.
Referring to the table or chart source in outputs
In columns, rows, X-axis values or group by fields in your output components, you can refer to table or chart source with {{currentItem}}
.
In table and charts where you are outputting a data series, {{currentItem}}
refers to the mapped element of an array, which is the source of your table.
In key value table where you are displaying a single object, {{currentItem}}
refers to the value inside an object, which is the source of your table.
On the above ShowIssues
example, if you add {{currentItem.id}}
to a column of a table output, that column is filled with the values of id and displays 219
in first row, 220
in second row.
On the above GetDeal example, if you add {{currentItem.id}}
to a row of a key value table output, that row is filled with the value of id and displays 865
.
If you add {{currentItem.priority}}
to X-axis values of a bar chart, the data gets grouped by the priority values and it produces a distribution by priority graph.
If you add {{currentItem.status}}
to Group by field of a pie chart, the data gets grouped by the status values and it produces a distribution by status graph.
Referring to other requests in outputs
In the above GetDeal
example, the response returns the deal owner's id but we want to show the name. The name can be retrieved from the response of the ListUsers
request. With applying find()
method, we can find the user with 397213
id and retrieve its name value dynamically.
{{request.ListUsers.response.body.users.find((user)=>user.id === currentItem.owner)?.name}}
// returns Thomas Davis
{{request.ListUsers.response.body.users}}
returns an array of users. find()
method finds the matching id with {{currentItem.owner}}
inside users array, and returns the name field. In our output, the deal owner will be shown as Thomas Davis
.
JS operators and common methods in Actioner
Comparison and logical operators
Comparison and logical operators are used to test for true or false. Comparison operators are used in logical statements to determine equality or difference between variables or values. Logical operators are used to determine the logic between variables or values.
Conditional (ternary) operator
Ternaries are one-line versions of if else statements. Since it is not possible to write if else logic in between {{ }}, use ternaries to return data conditionally.
<condition> ? <execute if condition is true> : <execute if condition is false>
Since statements in JavaScript are either truthy or falsy, the condition can be a statement with a comparison operator (e.g. === or >=), or just a reference to an input or a request component.
For example {{input.subject}}
. If a value for the subject input exists (has data, and is not empty), the condition will evaluate as true and the first function will execute.
Array methods
Whether you need to find the row of data based on the id selected from an input component, or append new values from an array pf an API request's response, you can apply JavaScript methods.
Given that the value of {{request.myRequest.response.body}}
returns ["P1", "P4", "P5", "P2", "P4", "P3"]
and {{request.myRequest2.response.body}}
returns ["Critical", "Minor", "High"]
; below list explains most common string methods:
.length: Returns the length of an array.
{{request.myResponse.response.body.myArray.length}}
// returns the total number of items in the response.
.map(): creates a new array with the results of calling a provided function on every element in this array.
{{request.myRequest.response.body.map(element=> "Priority: " + element)}}
// returns ["Priority: P1", "Priority: P4", "Priority: P5", "Priority: P2", "Priority: P4", "Priority: P3"]
.filter(): creates a new array with only elements that passes the condition inside the provided function.
{{request.myRequest.response.body.filter(element => element.id === "P3" || element.id === "P4")}}
// returns ["P4", "P4", "P3"]
.sort(): is used to arrange/sort array’s elements either in ascending or descending order.
{{request.myRequest.response.body.sort()}}
// returns ["P1", "P2", "P3", "P4", "P4", "P5"]
.concat(): is used to merge two or more arrays and returns a new array, without changing the existing arrays.
{{request.myRequest.response.body.concat(request.myRequest2.response.body)}}
// returns ["P1", "P4", "P5", "P2", "P4", "P3", "Critical", "Minor", "High"]
.every(): checks every element in the array that passes the condition, returning true or false as appropriate.
{{request.myRequest.response.body.every(element => element > P0)}}
// returns true
.some(): checks if at least one element in the array that passes the condition, returning true or false as appropriate.
{{request.myRequest.response.body.some(element => element === "P5")}}
// returns true
.includes(): checks if an array includes the element that passes the condition, returning true or false as appropriate.
{{request.myRequest.response.body.includes("P5")}}
// returns true
.join(): returns a new string by concatenating all of the array’s elements separated by the specified separator.
{{request.myRequest.response.body.join(', ')}}
// returns "P1, P4, P5, P2, P4, P3"
.find(): returns the value of the first element in an array that pass the test in a testing function.
{{request.myRequest.response.body.find(element => element > "P2")}}
// returns P4
.findIndex(): returns the index of the first element in an array that pass the test in a testing function.
{{request.myRequest.response.body.find(element => element === "P4")}}
// returns 1
.indexOf(): returns the index of the first occurrence of the specified element in the array, or -1 if it is not found.
{{request.myRequest.response.body.indexOf("P4")}}
// returns 1
.fill(): fills the elements in an array with a static value and returns the modified array.
{{request.myRequest.response.body.fill("P4")}}
// returns ["P4", "P4", "P4", "P4", "P4", "P4"]
.slice(): returns a new array with specified start to end elements.
{{request.myRequest.response.body.slice(2,4)}}
// returns ["P5", "P2"]
.reverse(): reverses an array in place. Element at last index will be first and element at 0 index will be last.
{{request.myRequest.response.body.reverse()}}
// returns ["P3", "P4", "P2", "P5", "P4", "P1"]
.push(): adds one or more elements to the end of array and returns the new length of the array.
{{request.myRequest.response.body.push("P2")}}
// returns ["P1", "P4", "P5", "P2", "P4", "P3", "P2"]
.pop(): removes the last element from the end of array and returns that element.
{{request.myRequest.response.body.pop()}}
// returns ["P1", "P4", "P5", "P2", "P4"]
.shift(): removes the first element from an array and returns that element.
{{request.myRequest.response.body.shift()}}
// returns ["P4", "P5", "P2", "P4", "P3"]
.unshift(): adds one or more elements to the beginning of an array and returns the new length of the array.
{{request.myRequest.response.body.shift("P3")}}
// returns ["P3", "P1", "P4", "P5", "P2", "P4", "P3"]
String methods
Given that the value of {{input.my_input}}
is userManagement,bug,critical
, below list explains most common string methods:
Getting string values
.slice(): it extracts the part of the string by taking start and end positions, and returns the new string.
{{input.my_input.slice(0, 4)}}
// returns user
.substring(): it works exactly the same as a slice but cannot take negative index.
{{input.my_input.substring(0,4)}}
// returns user
.substr():instead of ending position here, the second parameter takes the length of the extracted part.
{{input.my_input.substr(0, 4)}}
// returns user
Replacing content
.replace(): this method will replace the specified value with new values.
{{input.my_input.replace('user','order')}}
// returns orderManagement,bug,critical
.toUpperCase(): transform the string to uppercase.
{{input.my_input.toUpperCase()}}
// returns USERMANAGEMENT,BUG,CRITICAL
.toLowerCase(): transform the string to lower case.
{{input.my_input.toLowerCase()}}
// returns usermanagement,bug,critical
Extracting string characters
.charAt(): this method gives character at a specific index.
{{input.my_input.charAt(0)}}
// returns u
Property access: we can access any character of string using the index.
{{input.my_input[0]}}
// returns u
Converting a string to an array
.split(): split on will convert string to array based on the passed symbol.
{{input.my_input.split(",")}}
// returns [userManagement, bug, critical]
Joining string
Given that the value of {{input.my_input}}
is critical
and the value of {{input.my_input2}}
is bug
:
Template literals: can be used to join the string.
{{
${input.my_input} ${input.my_input2}}}
// returns critical bug
.concat(): joins two or more strings:
{{input.my_input.concat(" ",input.my_input2)}}
// returns critical bug
Removing spaces
Given that the value of {{input.my_input}}
is critical bug
:
.trim(): it removes the spaces from both sides of the string.
{{input.my_input.trim()}}
// returns critical bug