Difference between revisions of "Triggers"

From Memento Database Wiki
Jump to navigation Jump to search
m
m
 
(57 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{Stamp|2016-12-29|4.2.0|1.0.7}}
+
{{Stamp|2021-03-31|4.10.0|1.9.6}}
  
A Trigger is a script that defines the processing of an entry whenever a particular kind of event takes place. Trigger scripts are written in '''[https://developer.mozilla.org/en-US/docs/web/JavaScript JavaScript]'''. When a trigger script is executed, it may perform actions like changing an existing entry, creating a new entry, executing an HTTP request, creating a file, performing data validation, etc.
+
A '''''Trigger''''' is a script that defines the processing of an entry whenever a particular kind of event takes place. Trigger scripts are written in '''[https://developer.mozilla.org/en-US/docs/web/JavaScript JavaScript]'''. When a trigger script is executed, it may perform actions like changing an existing entry, creating a new entry, executing an HTTP request, creating a file, performing data validation, etc.
 
-----
 
-----
  
Line 8: Line 8:
 
:; Event type
 
:; Event type
 
:: One of the following:
 
:: One of the following:
 +
::* Opening a library
 
::* Creating an entry
 
::* Creating an entry
 
::* Updating an entry
 
::* Updating an entry
 +
::* Updating a field
 
::* Deleting an entry
 
::* Deleting an entry
 +
::* Linking an entry
 +
::* Unlinking an entry
 
::* Opening an Entry Edit card
 
::* Opening an Entry Edit card
 
::* Adding an entry to Favorites
 
::* Adding an entry to Favorites
Line 28: Line 32:
 
:; Asynchronous script execution mode: The script runs in the background; user interaction is '''not''' suspended. Usually, asynchronous scripts are used in the last phases of the action, after any save or other operation is initiated.
 
:; Asynchronous script execution mode: The script runs in the background; user interaction is '''not''' suspended. Usually, asynchronous scripts are used in the last phases of the action, after any save or other operation is initiated.
 
-----
 
-----
<br/>
+
=== Scripting for creating and for updating entries ===
 +
If you want the same logic to be used in both your ''Creating'' scripts and your ''Updating'' scripts, it is tempting to just copy the code for one and paste it into the other, and that can be  done in '''''in most cases'''''. If you write your ''Updating'' script first, the copy will work. If you write your ''Creating'' script first, then you '''''may''''' still be able to copy, but if you needed for the ''Creating'' Script to be executed ''Before saving'', you'll need to change ''defaultEntry()'' to use ''entry()'' instead.
  
 
== Events & Phases ==
 
== Events & Phases ==
Line 36: Line 41:
 
|-
 
|-
 
! Event type !! Phase !! [[#Mode of script execution|Execution mode]]
 
! Event type !! Phase !! [[#Mode of script execution|Execution mode]]
 +
|-
 +
|| '''[[#Opening a library|Opening a library]]'''
 +
| '''Opening the library''' || synchronous
 
|-
 
|-
 
| rowspan="3" | '''[[#Creating an entry|Creating an entry]]'''
 
| rowspan="3" | '''[[#Creating an entry|Creating an entry]]'''
Line 47: Line 55:
 
| '''Opening an Entry Edit card''' || synchronous
 
| '''Opening an Entry Edit card''' || synchronous
 
|-
 
|-
 +
| '''Before saving the entry''' || synchronous
 +
|-
 +
| '''After saving the entry''' || asynchronous
 +
|-
 +
| rowspan="2" | '''[[#Linking an entry|Linking an entry]]'''
 +
| '''Before saving the entry'''  || synchronous
 +
|-
 +
| '''After saving the entry''' || asynchronous
 +
|-
 +
| rowspan="2" | '''[[#Unlinking an entry|Unlinking an entry]]'''
 
| '''Before saving the entry''' || synchronous
 
| '''Before saving the entry''' || synchronous
 
|-
 
|-
Line 71: Line 89:
 
| '''After the operation''' || asynchronous
 
| '''After the operation''' || asynchronous
 
|}
 
|}
 +
 +
=== Opening a library ===
 +
This event occurs when a library is opened before any activity is performed on the library.
 +
This event has only one phase, and it is ''synchronous''. Therefore, the script will fully execute before any other activity takes place in the library.
  
 
=== Creating an entry ===
 
=== Creating an entry ===
Line 81: Line 103:
  
 
:; 3. After saving the entry (asynchronous): The script will be called once the save of the entry has been initiated and will continue in parallel with final processing of the new entry.<br/>If you want to do something whenever a new entry is added, but not before the data is saved, this is the place to do it.
 
:; 3. After saving the entry (asynchronous): The script will be called once the save of the entry has been initiated and will continue in parallel with final processing of the new entry.<br/>If you want to do something whenever a new entry is added, but not before the data is saved, this is the place to do it.
 +
 +
==== Scripting considerations ====
 +
 +
'''=>''''' Say what here? '''''
 +
 +
===== Creating an entry =====
 +
''Creating'' scripts are different from other trigger scripts in that, since the new entry is being crafted, it is not yet stored within Memento &mdash; the Entry object does not yet exist &mdash; so it needs to be handled differently.
 +
 +
Use the method ''defaultEntry()'', rather than ''entry()'', to get the DefaultEntry object (the ''un''stored template, if you will, for the eventual Entry object).
 +
 +
Once the default entry is saved, it can be referred to by calling ''entry()'' to get the saved Entry object. So ''Before saving'' scripts use ''defaultEntry()'', and ''After saving'' scripts use ''entry()''.
  
 
=== Updating an entry ===
 
=== Updating an entry ===
Line 91: Line 124:
  
 
:; 3. After saving the entry (asynchronous): The script will be called once the save of the entry has been initiated and will continue in parallel with final processing of the new entry.<br/>If you want to do something whenever an existing entry is updated, but not before the data is saved, this is the place to do it.
 
:; 3. After saving the entry (asynchronous): The script will be called once the save of the entry has been initiated and will continue in parallel with final processing of the new entry.<br/>If you want to do something whenever an existing entry is updated, but not before the data is saved, this is the place to do it.
 +
 +
'''<i><b>The following 2 sections are under development.</b></i>'''
 +
 +
=== Updating a field ===
 +
TBD
 +
 +
=== Linking an entry ===
 +
This event starts when a user has added a linked entry to a Link to Entry field either by selecting an existing entry from the entries list for the linked library or by pressing '''+''' (Plus) to create a new entry in the linked library. It ends after the entry is resaved to storage &mdash; or else not, depending on the processing of the entry.
 +
==== Phases ====
 +
This event has two phases. In sequence:
 +
:; 1. Before saving the entry (synchronous): The script will be called before the entry has been saved to storage after the user has either selected an entry from the linked library or pressed the '''Checkmark''' icon after having added a new entry to the linked library from within the Link to Entry field.<br/>The entered data may be validated; if validation fails, the save may be forestalled and control sent back to the Entry Edit card so the user may fix the errors.<br/>Once the data is validated, this phase is an opportunity for many other functions to be performed to get data to add to the entry to be saved.<br/>The script could be used to update some entry data based on other entry data, to keep it all coordinated.
 +
 +
:; 2. After saving the entry (asynchronous): The script will be called once the save of the entry has been initiated and will continue in parallel with final processing of the new entry.<br/>If you want to do something whenever an existing entry is updated, but not before the data is saved, this is the place to do it.
 +
 +
The script of this event type has access to additional functions:
 +
===== masterLib() ===== 
 +
:Returns the library from which the current record was referenced.
 +
===== masterEntry() =====
 +
:Returns the entry from which the current record was referenced.
 +
===== attr(name) =====
 +
:Get the value of the attribute for the current reference.
 +
===== setAttr(name , value) =====
 +
:Set the value of the attribute for the current reference.
 +
 +
=== Unlinking an entry ===
 +
This event starts when a user has requested that an existing entry be edited (updated) within the library, normally by pressing the '''Pencil''' button on the Entry View card. It ends after the entry is resaved to storage &mdash; or else not, depending on the processing of the entry.
 +
==== Phases ====
 +
This event has two phases. In sequence:
 +
:; 1. Before saving the entry (synchronous): The script will be called after the user has pressed the '''Checkmark''' icon and before the entry has been saved to storage.<br/>The entered data may be validated; if validation fails, the save may be forestalled and control sent back to the Entry Edit card so the user may fix the errors.<br/>Once the data is validated, this phase is an opportunity for many other functions to be performed to get data to add to the entry to be saved.<br/>The script could be used to update some entry data based on other entry data, to keep it all coordinated.
 +
 +
:; 2. After saving the entry (asynchronous): The script will be called once the save of the entry has been initiated and will continue in parallel with final processing of the new entry.<br/>If you want to do something whenever an existing entry is updated, but not before the data is saved, this is the place to do it.
  
 
=== Deleting an entry ===
 
=== Deleting an entry ===
Line 153: Line 217:
  
 
=== Writing a Trigger Script ===
 
=== Writing a Trigger Script ===
Trigger scripts are JavaScripts. See '''''[[#JavaScript links|links to JavaScript documentation]]''''' below.
+
Trigger scripts are in the JavaScript language. See '''''[[#JavaScript links|Links to JavaScript documentation]]''''' below.
  
 
; Event & Phase: Make sure to understand the Event & Phase you are scripting. This will dictate a number of things, such as whether data is present already or not, whether ''cancel()'' makes sense or not, whether the user is waiting for script execution or not, and so on.
 
; Event & Phase: Make sure to understand the Event & Phase you are scripting. This will dictate a number of things, such as whether data is present already or not, whether ''cancel()'' makes sense or not, whether the user is waiting for script execution or not, and so on.
Line 166: Line 230:
  
 
; No return: As of release 4.0.0 of the mobile edition, the script is executed as a top-level script and not as a called function; therefore, for instance, the ''return'' statement is not appropriate in a trigger script.
 
; No return: As of release 4.0.0 of the mobile edition, the script is executed as a top-level script and not as a called function; therefore, for instance, the ''return'' statement is not appropriate in a trigger script.
-----
 
<br/>
 
 
== Libraries and Entries ==
 
=== ''Library'' Global Functions ===
 
 
==== entry() ====
 
: Get the entry of the Event. For example, if script is triggered by an Update Entry event, this function will return the entry being updated.
 
 
: This function is available to all Events and Phases, with the exception of '''Creating an entry''' &gt; '''Opening an Entry Edit card'''; for this action, use the function ''entryDefault()''.
 
 
:; Result: [[#Object Entry|'''''Entry''''' object]] &mdash; the current entry
 
 
==== entryDefault() ====
 
: Get the Entry object containing the default field values for the entry not yet created. This feature is available specifically and only for the Event '''Creating an entry''' & Phase '''Opening an Entry Edit card'''.
 
 
:; Result: [[#Object DefaultEntry|'''''DefaultEntry''''' object]]
 
 
==== lib() ====
 
: Get the library of the triggering event
 
 
:; Result: [[#Object Library|'''''Library''''' object]] &mdash; the current library
 
 
==== libByName(name) ====
 
: Find the library by name. Permission to use the library is required, based on security settings.
 
 
:; Argument: The name of the library to find
 
 
:; Result: [[#Object Library|'''''Library''''' object]] &mdash; the library identified by the argument ''name'', if found. Otherwise ''null''.
 
 
<br/>
 
 
=== Object ''Library'' ===
 
This object provides access to library entries. You can work with the current library &mdash; the ''lib()'' &mdash; or any other library in the database &mdash; ''libByName()''. This function provides the ability to update existing entries and create new ones.
 
 
==== ''Library'' Methods ====
 
===== entries() =====
 
: Get all the entries of the library
 
:; Result: Array object containing entries, sorted by the time of their creation, from newest to oldest
 
 
===== find(query) =====
 
: Search field values within entries in the library matching the given query. This search is similar to searching via Memento's user interface.
 
:; Argument: '''query''' &mdash; the search string
 
:; Result: Array object containing matching entries. Entries are sorted by the time of their creation, from newest to oldest.
 
 
===== findByKey(name) =====
 
: Search all entries by the Entry Name. The library must be set for unique Entry Names.
 
:; Argument: '''name''' &mdash; the Entry Name field value
 
:; Result: [[#Object Entry|'''''Entry''''' object]], if found. Otherwise, ''null''.
 
 
===== create(values) =====
 
: Create a new entry in the library
 
:; Argument: '''values''' &mdash; [https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object '''''Object'''''] containing the field values
 
:; Result: [[#Object entry|'''''Entry''''' object]] &mdash; the new entry in the library
 
 
===== show() =====
 
: Display the library
 
 
==== ''Library'' Properties ====
 
: '''title''' &mdash; The name of the library
 
 
<br/>
 
 
=== Object ''Entry'' ===
 
This object holds an entry of the current library, allowing the setting of field values
 
 
==== ''Entry'' Methods ====
 
===== set(name, value) =====
 
: Set the value of the named field. Calling this method results in immediate writing of the value to the library.
 
 
:'''Note:''' In the case where the field to be set is a Link To Entry field, the second argument should be the entry name of the entry to link to. If there are, in that library, multiple fields set as Entry Name, the entry name for the set() method is formed by separating the value of those fields by a comma (,) &mdash; not a comma and a space, but only a comma.
 
 
:; Arguments
 
:: '''name''' &mdash; name of the field
 
:: '''value''' &mdash; the value of the field
 
 
===== field(name) =====
 
: Get the value of the named field
 
:; Argument: '''name''' &mdash; name of the field
 
:; Result: The value of the field. The type of the result depends on the type of the field.
 
 
===== show() =====
 
: Display the entry
 
 
==== ''Entry'' Properties ====
 
: '''title''' &mdash; entry name
 
: '''description''' &mdash; entry description
 
: '''favorites''' &mdash; true, if the entry is in Favorites
 
: '''deleted''' &mdash; true, if the record is deleted (it is in the Recycle Bin)
 
<br/>
 
 
=== Object ''DefaultEntry'' ===
 
Template with default values for a new entry. This feature is available specifically and only for the Event '''Creating an entry''' & Phase '''Opening an Entry Edit card'''.
 
 
==== ''DefaultEntry'' Methods ====
 
===== set(name, value) =====
 
: Set the value of the field
 
 
:; Arguments
 
:: '''name''' &mdash; the name of the field
 
:: '''value''' &mdash; the value of the field
 
<br/>
 
 
=== Libraries Examples ===
 
-----
 
==== Data Validation ====
 
Using scripts, you can check the correctness of input data and deny saving of data that fail the test. For example, perhaps a field integer values ​​are allowed only from 0 to 200.
 
 
:; Add a Create trigger: Set Event to '''Creating a new entry''', Phase to '''Before saving the entry'''. It will run synchronously.
 
:; Add an Update trigger: Set Event to '''Updating an entry''', Phase to '''Before saving the entry'''. It will run synchronously.
 
:'''Trigger script:'''
 
<source lang="javascript">
 
var num = entry().field("Number"); // Get value of field Number
 
if (num < 0 || num > 200) {        // Test for value matching allowable range
 
message("Wrong range");          // If value is outside range, display message
 
cancel();                        // Cancel the operation
 
}
 
</source>
 
 
:; Set default values: If default values cannot be set using the user interface, they can be set using a script.
 
<br/>
 
-----
 
===== Previous value of another field =====
 
Suppose there is a library containing daily mileage of daily walks or use of a car or bicycle. Suppose the library has a StartingMileage field and a Mileage field. When an entry is created, the field StartMileage must get data from the field Mileage in the previous entry.
 
 
:; Add a new trigger: Set Event to '''Creating an entry''' and Phase to '''Open the Entry Edit card'''. It will run synchronously.
 
 
:'''Trigger script:'''
 
<source lang="javascript">
 
var entries = lib().entries();                  // Get current library & array of its entries
 
if (entries.length > 0) {                        // Check that array is not empty;
 
                                                //  otherwise exit,
 
                                                //  since there is no previous entry.
 
prevMileage = entries[0].field("Mileage");      // The array is sorted newest to oldest,
 
                                                //  so newest entry in the array
 
                                                //  on top with index of 0.
 
entryDefault().set("StartMileage", prevMileage); // Set value of field Mileage
 
                                                //  from the previous entry
 
                                                //  as default value for field StartMileage.
 
}
 
</source>
 
<br/>
 
-----
 
 
===== Beginning of the next day =====
 
Suppose you need to identify the beginning of a new day in the DateTime field. (The script requires connection of the JavaScript library '''moment.js''' [http://momentjs.com/ moment.js].)
 
 
:; Add new trigger: Set Event to '''Creating an entry''' and Phase to '''Opening an Entry Edit card'''. It will run synchronously.
 
:'''Trigger script:'''
 
<source lang="javascript">
 
var m = moment().add(1,'d'); // Using the moment.js library function moment(),
 
                            //  get the current time, and add 1 day
 
m.hour(8).minute(0);        // Set the time to hour 8 and minute 0
 
entryDefault().set("Date",
 
    m.toDate().getTime());  // Use that as the default value for the field Date
 
</source>
 
<br/>
 
-----
 
 
===== New entry in one library triggers new entry in another =====
 
Suppose that after a new application for membership is validated, a new member should be created. We have libraries Applications & Members. After a new Applications entry is entered and before it is saved, we want to validate the application, and if it passes, we want to create a new entry in Members.
 
 
:; Fields: Library Applications has fields '''Date''', '''Name''', City, Years in city, Type.<br/> Library Members has fields '''Name''' & Type.
 
:; Set library permission: The Applications library must be permitted to access other libraries via ''libByName()''.<br/>Within Triggers, press the shield icon to view Permission settings.
 
:; Add new trigger: Add a new trigger to the Applications library.<br/>Set Event '''Creating a new entry''', Phase '''Before saving the entry'''.<br/>(A similar trigger could be set for Event '''Updating an entry''' Phase '''Before saving the entry''', but only if a change in a field value is to trigger the add of the new member.)
 
 
:'''Trigger script:'''
 
<source lang="javascript">
 
// Get the application entry that's being saved and call it e
 
//    (because you may use it a lot)
 
// With that, you can readily reference fields
 
//    in the new application entry about to be saved
 
 
var e = entry();
 
 
// If you have any checks on the values of fields the user entered,
 
//    you could do them here
 
// If any validations fail, call cancel() to forestall the save
 
// The user remains in the Entry Edit card
 
//    and will be advised the application does not pass criteria
 
 
if (e.field("Years in city") <== 2) {  // Sample field validation
 
    message("Application inadequate"); // You'll want more
 
    cancel(); // This will abort the save, but continue the script
 
}
 
 
else {      // Add a new member only if application is adequate
 
 
// From here on, the new application will be saved,
 
//    so we must also create the new member entry
 
 
// To create the new member, we need to reference Members
 
 
    var members = libByName("Members"); // This requires permission (see above)
 
 
// Start a new entry for Members
 
 
    var newMember = new Object();
 
 
// Set member fields from application data and add the new Members entry
 
 
    newMember["Name"] = e.field("Name");
 
    newMember["Type"] = e.field("Type");
 
    members.create(newMember);
 
}
 
</source>
 
<br/>
 
-----
 
<br/>
 
===== Ensuring unique non-Name field value =====
 
The goal is to ensure that a particular field value is unique within the library.
 
 
The ideal way to do this is to make the field the one and only Entry Name field and then set the Entry Name to be unique. Then, Memento will take care of ensuring this for you.
 
 
To do this, go in the Library Edit screen to the MAIN tab of the library and turn on the switch "The Entry Name is unique". Then go to the FIELDS tab and ensure that your field and only that field has the role Entry Name. Do that by editing the field, and under Display Options, you'll see "Display in the list as"; the choices will include Entry Name.
 
 
Now, if your library needs some other Entry Name, and you nevertheless want to ensure that a different field is unique within the library, then, yes, you'll need some code.
 
 
The best way, if you're just starting to enter entries into your library, is to make sure they're unique from the outset, so from the Entries List screen, pick Triggers from the menu. Press the + (plus) button to add a trigger, set the Event to "Creating a new entry" and the Phase to "Before saving the entry".
 
 
:; Fields: The field in the current library &mdash; that is to be unique among the entries of the library &mdash; is ''myField''.
 
:; Add new trigger: Add a new trigger to the library.<br/>Set Event '''Creating a new entry''', Phase '''Before saving the entry'''.<br/>(A similar trigger could be set for Event '''Updating an entry''' Phase '''Before saving the entry''' &mdash; for instance, if the library already has entries in which ''myField'' may not be unique.)
 
 
:'''Trigger script:'''
 
<source lang="javascript">
 
var myField = entry().field("myField");            // Value of myField
 
var entries = lib().entries();                    // Array containing all entries
 
 
var unique = true;                                // Presuming, initially
 
for (var ent = 0; ent < entries.length; ent++) {  // Loop through all entries
 
    if (entries[ent].field("myField") === myField) // If there is ever a match,
 
        unique = false;                            // Remember it
 
}
 
 
if (!unique) { // If not unique,
 
    cancel(); // Disallow the save
 
    message("myField is not unique. Try again.");  // Tell the user
 
}
 
</source>
 
<br/>
 
-----
 
<br/>
 
 
== Working with files ==
 
With scripts, you can read or write files located in the device's internal memory or on the SD card. All file operations are performed by the ''File'' object, which is obtained via a global function called ''file()''.
 
 
To work with the files, the library should have read/write file access.
 
 
=== ''File'' Global Functions ===
 
==== file(name) ====
 
Open a file for read or write operations. If the file with the specified name does not exist yet, it will be created.
 
 
:; Argument
 
:: '''name''' &mdash; The name and the full path to the file. For example, if the file is located on the SD card, the path should be something like /sdcard/example.txt.
 
 
:; Result
 
:: [[#Object File|'''''File''''' object]]
 
<br/>
 
 
=== Object ''File'' ===
 
This object is returned by the global function ''file()'' and provides access to the requested file. After reading or writing, the file should be closed using the method ''close()''.
 
 
==== ''File'' Methods ====
 
; readAll()
 
: Reads all lines of the file, and then closes the file
 
:; Returns
 
:: Array containing the lines of the file
 
 
; readLine()
 
: Reads the next line from the file stream
 
:; Returns: The line
 
 
; readLines()
 
: Reads the remaining lines from the file stream
 
:; Returns: Array containing the remaining lines of the file
 
  
; readChar()
+
==== Things to know while writing a trigger script ====
: Reads the next character from the file stream
+
:; No implicit context: There is no implicit context for the trigger script, as there is, for instance, in a JavaScript field. Instead, there are global functions, such as lib() and entry() that must be used to set up context for the script.
:; Returns: The character
+
:; Entry objects are clones: The Entry object associated with the Event (the one that entry() gives you) is a clone of the actual entry object. If changes are made to this object, they will be saved if the script returns normally. However, if the script calls cancel(), this clone will be discarded upon return from the script, along with any changes that have been made.
 
 
; write(text)
 
: Write string(s). Take a variable number of arguments, converts each argument to a string, and writes that string to the file stream.
 
 
 
; writeLine(text)
 
: Write strings and a newline to the file stream
 
 
 
; close()
 
: Close the file. It can subsequently be reopened.
 
 
 
==== ''File'' Properties ====
 
:; exists: true &mdash; if and only if the file exists; false otherwise
 
:; length: The length, in bytes, of the file, or 0L if the file does not exist.  
 
:; getLineNumber: Get the current line number
 
::; Returns: The line number, or position, in the file
 
<br/>
 
 
 
=== Files Examples ===
 
 
-----
 
-----
==== Writing & reading from a file ====
 
 
:; Add trigger(s): This script could be a part of any phase of any event.
 
 
:'''Trigger script:'''
 
<source lang="javascript">
 
f = file("/sdcard/myfile.txt"); // Open myfile.txt on the SD card
 
                                // If no file, it will be created
 
f.writeLine("one");            // Write "one" as a line to the file
 
f.writeLine("two");
 
f.writeLine("three");
 
f.close();                      // Close & save. Until closed,
 
                                //  the file is still empty
 
var a = f.readLines();          // Read all lines into array a
 
</source>
 
 
<br/>
 
<br/>
-----
 
==== Save an entry to a file in XML format ====
 
The entry includes fields: id , title , date.
 
  
:; Add trigger(s): This script could be a part of any phase of any event.
+
== See Also ==
 
+
; [[Memento JavaScript Library]]: Memento JavaScript functions & objects<br/>
:'''Trigger script:'''
+
; [[Trigger Examples]]: Examples of trigger scripts for common needs
<source lang="javascript">
+
; [[How:Write scripts in JavaScript for Memento]]: Guidelines for writing JavaScript scripts for Memento
var xml = '<record id="' + entry().field("id") + '">' +  // Format XML record
 
'<title>' + entry().field("title") + '</title>' +        //    from entry field values
 
'<date>' + entry().field("date") + '</date>' +
 
'</record>';
 
f = file("/sdcard/" + entry().field("title") + ".xml");  // File name is Entry Name
 
f.write(xml);                                            // Save XML data to the file
 
f.close();                                              // Close the file
 
</source>
 
<br/>
 
-----
 
<br/>
 
 
 
== Processing an HTTP request ==
 
Scripts can send HTTP requests to Web services through their APIs.
 
Processing for HTTP requests allows integration between Memento and the system. All file operations use the Http object, which works through global function ''http()''.
 
 
 
HTTP requests must fulfill two requirements:
 
# Script execution must be '''asynchronous''', so HTTP requests go in the last Phase of an Event.
 
# The library should have the permission '''Network'''.
 
<br/>
 
 
 
=== Object ''Http'' ===
 
Interface for processing HTTP requests
 
 
 
==== ''Http'' Methods ====
 
===== get(url) =====
 
: Execute HTTP get request
 
:; Argument: '''url''' &mdash; HTTP address, starting with http or https
 
:; Result: '''HttpResult''' &mdash; Object containing the result of the execution of the HTTP request
 
<br/>
 
 
 
=== Object ''HttpResult'' ===
 
Result of the execution of the HTTP request
 
 
 
==== ''HttpResult'' Properties ====
 
: '''code''' &mdash; HTTP code of the response, if the request is successful (usually 200).
 
: '''body''' &mdash; The response in text form
 
<br/>
 
 
 
=== Http Examples ===
 
-----
 
==== Currency Conversion ====
 
Suppose the library contains two fields: PriceUSD and PriceEUR.
 
The user will enter the value in PriceUSD and the value in Euros will appear in PriceEUR according to the current exchange rate.
 
 
 
:; Add new trigger: Set Event '''Create a new entry''' and Phase '''After saving the entry'''. It will run asynchronously.
 
 
 
:'''Trigger script:'''
 
<source lang="javascript">
 
result = http().get("http://api.fixer.io/latest?base=USD"); // Use http://fixer.io/ for
 
                                                            // conversion rate in JSON
 
usdToEur = JSON.parse(result.body)["rates"]["Eur"];        // Use JavaScript object JSON
 
                                                            // to parse the result
 
entry().set("PriceEUR",
 
    entry().field( "PriceUSD") * usdToEur );                // PriceUSD * conversion rate
 
                                                            // for value for PriceEUR
 
</source>
 
<br/>
 
-----
 
 
 
==== Creating a Task in the Todoist App ====
 
[https://todoist.com Todoist] — A Web service and mobile app for task management. Todoist allows task creation via API [https://developer.todoist.com/].
 
In the following example of task creation, text will be taken from the Memento library entry.
 
 
 
:; Add a new trigger: Set Event to '''Creating a new entry''' or perhaps '''Update an entry'''. Set Phase as appropriate.
 
:'''Trigger script:'''
 
<source lang="javascript">
 
// Create a JSON command for Todoist task creation
 
//    using the format described in [https://develop.todoist.com/#add-an-item].
 
// This command should include a unique identifier
 
//    created using the guid() global function.
 
var commands =
 
    '[{"uuid":"' + guid() + '","temp_id":"' + guid() +
 
    '","type":"item_add","args":{"content":"' + entry().field("Task") + '"}}]';
 
 
 
// Execute the HTTP request. An attribute called '''token''' is used
 
//    for authorization in Todoist.
 
// It is available in the Todoist Account setting.
 
// Since the text of the task can include symbols
 
//    not allowed in a URL request,
 
//    use the standard function encodeURIComponent() to filter them out.
 
result = http().get("https://todoist.com/API/v7/sync?token=15281e8e4d499dаff817af0b14112eac3176f9dc&commands=" +
 
    encodeURIComponent(commands));
 
 
 
// Show the user a message indicating successful creation of the task.
 
if (result.code == 200)
 
    message('Task has been successfully created");
 
</source>
 
<br/>
 
-----
 
<br/>
 
 
 
== Built-in objects for certain Memento field types ==
 
=== Object ''JSContact'' ===
 
This object contains the information stored within a Contact field and provides properties and methods for use in accessing and manipulating this information.
 
 
 
When an Entry object's field() method is called, if the Memento field type is Contact, a JSContact object is returned.
 
 
 
If the Contact field contains multiple contacts, use ''hasNext'' and ''next'' to retrieve them.
 
 
 
==== JSContact Methods ====
 
 
 
:; show(): Opens the Contacts app for this contact
 
 
 
:; call(): If the device is a phone, calls the primary phone number of this contact
 
 
 
:; sendSMS(message): If the device is a phone, sends the provided '''''message''''' (text string) as an SMS message to the primary phone number of this contact
 
 
 
:; sendEmail(subject, message): Sends an email message to the primary email address of this contact, with subject '''''subject''''' and message '''''message''''' (text string)
 
 
 
==== JSContact Properties ====
 
 
 
:; fullName: The full name of this contact
 
 
 
:; phone: The primary phone number of this contact
 
 
 
:; email: The primary email address of this contact
 
 
 
:;hasNext: Returns TRUE if there is a next JSContact object, otherwise FALSE
 
 
 
:;next: Returns the next JSContact object, if there is one.
 
 
 
=== Object ''JSGeolocation'' ===
 
 
 
This object contains the information stored within a Location field and provides properties and methods for use in accessing and manipulating this information.
 
 
 
When an Entry object's field() method is called, if the Memento field type is Location, a JSGeolocation object is returned.
 
 
 
If the Location field contains multiple locations, use ''hasNext'' and ''next'' to retrieve them.
 
 
 
==== JSGeolocation Properties ====
 
 
 
:; lat: Latitude, as a Real
 
 
 
:; lng: Longitude, as a Real
 
 
 
:; address: Address for this Location
 
 
 
:; hasNext: Returns TRUE if there is a next JSGeolocation object, otherwise FALSE
 
 
 
:; next: Returns the next JSGeolocation object, if there is one.
 
 
 
<br/>
 
-----
 
<br/>
 
 
 
== Interaction with the System ==
 
=== ''System'' Global Functions ===
 
==== message(text) ====
 
: Shows the user a brief notification
 
:; Argument: '''text''' &mdash; Text of the notification
 
 
 
==== cancel() ====
 
: Stop the system operation that caused the event. Many triggers can be a result of an entry manipulation (create, update, delete, etc). The ''cancel()'' function can be used during the phases that precede the system operation. For example, this function can be used during data validation before the entry is saved.
 
 
 
==== system() ====
 
: Obtain information about the system
 
:; Result: [[#Object System|'''''System''''' object]]
 
 
 
==== log(text) ====
 
: Write a line to the log file
 
:; Argument: '''text''' &mdash; text to be written to the log
 
 
 
==== guid() ====
 
: Generates random text identifier
 
:; Result: Random string identifier
 
 
 
==== intent(action) ====
 
: Create an information exchange object &mdash; Intent. This function can send a request for action to another application.
 
: This function is available only on Android.
 
:; Argument: '''action''' &mdash; Line that defines standard action (eg, view, pick)
 
:; Result: [[#Object Intent|'''''Intent''''' object]] &mdash; Information exchange object
 
: After the object is received, the data will be added to it, and then sent via ''send()''.
 
: Android has many built-in actions. A list of these actions can be found [https://developer.android.com/reference/android/content/Intent.html '''here'''].
 
<br/>
 
 
 
=== Object ''System'' ===
 
This object contains information about the system.
 
 
 
==== ''System'' Properties ====
 
: '''os''' &mdash; Name of the operating system executing the script
 
<br/>
 
 
 
=== Object ''Intent'' ===
 
Information exchange object. This object is created by using the global function ''intent()''.
 
 
 
==== ''Intent'' Methods ====
 
===== data(uri) =====
 
: Define URI to reference the data
 
:; Argument
 
:: uri &mdash; URI referencing data to which the action will be applied. It can be contact ID, path to the file, phone number, etc.
 
 
 
===== mimeType(mime) =====
 
: Define MIME type of the data
 
:; Argument
 
:: mime &mdash; MIME type of the data on which the operation will be performed
 
 
 
===== extra(key, value) =====
 
: Define additional data as key-value pairs, as necessary for execution of the required action. Similar to how URI data can be required for certain actions, other actions may require extra data in this format.
 
:; Arguments
 
:: key and value
 
 
 
===== extraLong(key, value) =====
 
: Define additional data as key-value pairs, where data type needs to be Long
 
:; Arguments
 
:: key and value
 
 
 
===== send() =====
 
: Send a message
 
<br/>
 
 
 
=== System Examples ===
 
-----
 
==== Script to open a screen for dialing a number ====
 
Suppose a library contains a field called Phone containing a phone number.
 
 
 
:; Add a new trigger: Set Event to '''Opening an Entry View card''', Phase to '''After display of the entry'''.
 
 
 
:'''Trigger script:'''
 
<source lang="javascript">
 
i = intent("android.intent.action.DIAL"); // Create information exchange object Intent
 
                                          // with the action of DIAL
 
i.data("tel:"+entry().field("Phone"));    // The data will be the phone number obtained
 
                                          // from the field Phone
 
i.send();                                // Send the message
 
</source>
 
<br/>
 
-----
 
==== Script to open app to send SMS message ====
 
The phone number will be obtained from the field Phone and the text of the message will be obtained from the fields ContactName and Notes.
 
 
 
:; Add a new trigger: Set Event to '''Opening an Entry View card''', Phase '''After display of the entry'''. It will run synchronously.
 
 
 
:'''Trigger script:'''
 
<source lang="javascript">
 
msg = "Dear, " +
 
    entry().field("ContactName") +
 
    "\n" + entry().field("Notes");          // Form the message from ContactName & Notes
 
i = intent("android.intent.action.SENDTO"); // Create intent object to open the app for sending
 
i.data("smsto:"+entry().field("Phone"));    // Provide the phone number in format smsto:number
 
i.extra("sms_body" , msg);                  // Insert the text of the message to sms_body
 
i.send();                                  // Send the message
 
</source>
 
<br/>
 
-----
 
==== Script to insert an appointment into Google Calendar ====
 
Suppose a library contains the time and name of an appointment.
 
 
 
:; Add a new trigger: Set Event to '''Creating a new entry''', Phase to '''Before saving the entry'''. It will run synchronously.
 
 
 
:'''Trigger script:'''
 
<source lang="javascript">
 
i = intent("android.intent.action.INSERT");      // Create Intent object
 
i.data("content://com.android.calendar/events");  // Data contains Google Calendar URI
 
i.extra("title", entry().field("Title"));        // Get event name from field Title
 
i.extra("description",
 
    entry().field("Description"));                // Get description from field Description
 
i.extraLong("beginTime",
 
    entry().field("Begin").getTime());            // Get start time from field Begin
 
                                                  // Begin is of type DateTime
 
                                                  // Additional parameter is of type Long,
 
                                                  // so extraLong() is used for conversion.
 
i.extraLong("endTime",
 
    entry().field("End").getTime());              // Get end time from the field End
 
                                                  // Requires same conversion as above
 
i.send();                                        // Send the message
 
</source>
 
<br/>
 
-----
 
<br/>
 
  
== JavaScript links ==
+
[[Category:en]] [[Category:Spec]]
{{Template:JavaScriptLinks}}
 

Latest revision as of 14:29, 17 August 2021

« Page as of 2021-03-31, editions Mobile 4.10.0, Desktop 1.9.6 »

A Trigger is a script that defines the processing of an entry whenever a particular kind of event takes place. Trigger scripts are written in JavaScript. When a trigger script is executed, it may perform actions like changing an existing entry, creating a new entry, executing an HTTP request, creating a file, performing data validation, etc.


Definitions

We define the following terms:

Event type
One of the following:
  • Opening a library
  • Creating an entry
  • Updating an entry
  • Updating a field
  • Deleting an entry
  • Linking an entry
  • Unlinking an entry
  • Opening an Entry Edit card
  • Adding an entry to Favorites
  • Removing an entry from Favorites
Phase of an Event
One of a predefined set of moments during entry processing for each Event type during which the user can intervene via a trigger script. See the table of events and phases below.
The Event type and the Phase determine the trigger script(s) to be run.
Trigger or Trigger Script
A script that may be defined to run when an event occurs for an entry
The trigger (Event type & Phase) and the corresponding trigger script are one-to-one.
When referring specifically to the script, it is called the trigger script. When referring to the Event type & Phase and its listing in the trigger list, it is referred to merely as a trigger.

Mode of script execution

The phase in which the trigger is activated defines its mode of execution — synchronous or asynchronous.

Synchronous script execution mode
Memento suspends user interaction and then executes the script. In the case of Before... phases, there is an impending operation that will take place unless the script forestalls it by calling cancel().
Time-consuming operations are not recommended in this mode.
Asynchronous script execution mode
The script runs in the background; user interaction is not suspended. Usually, asynchronous scripts are used in the last phases of the action, after any save or other operation is initiated.

Scripting for creating and for updating entries

If you want the same logic to be used in both your Creating scripts and your Updating scripts, it is tempting to just copy the code for one and paste it into the other, and that can be done in in most cases. If you write your Updating script first, the copy will work. If you write your Creating script first, then you may still be able to copy, but if you needed for the Creating Script to be executed Before saving, you'll need to change defaultEntry() to use entry() instead.

Events & Phases

These are the defined Event types, Phases, and their corresponding modes of execution.

THE DEFINED EVENT TYPES & THEIR PHASES
Event type Phase Execution mode
Opening a library Opening the library synchronous
Creating an entry Opening an Entry Edit card synchronous
Before saving the entry synchronous
After saving the entry asynchronous
Updating an entry Opening an Entry Edit card synchronous
Before saving the entry synchronous
After saving the entry asynchronous
Linking an entry Before saving the entry synchronous
After saving the entry asynchronous
Unlinking an entry Before saving the entry synchronous
After saving the entry asynchronous
Deleting an entry Before deleting the entry synchronous
After deleting the entry asynchronous
Opening an Entry View card Before window display synchronous
After window display asynchronous
Adding an entry to Favorites Before the operation synchronous
After the operation asynchronous
Removing an entry from Favorites Before the operation synchronous
After the operation asynchronous

Opening a library

This event occurs when a library is opened before any activity is performed on the library. This event has only one phase, and it is synchronous. Therefore, the script will fully execute before any other activity takes place in the library.

Creating an entry

This event starts when a user has requested that a new entry be added to the library, normally by pressing the + button on the Entries List screen. It ends after the entry is saved to storage — or else not, depending on the processing of the entry.

Phases

This event has three phases. In sequence:

1. Opening an Entry Edit card (synchronous)
The script will be called once the Entry Edit card is open and before any data is displayed.
Initial field values (defaults) may be set.
2. Before saving the entry (synchronous)
The script will be called after the user has pressed the Checkmark icon and before the entry has been saved to storage.
The entered data may be validated; if validation fails, the save may be forestalled and control sent back to the Entry Edit card so the user may fix the errors.
Once the data is validated, this phase is an opportunity for many other functions to be performed to get data to add to the entry to be saved.
3. After saving the entry (asynchronous)
The script will be called once the save of the entry has been initiated and will continue in parallel with final processing of the new entry.
If you want to do something whenever a new entry is added, but not before the data is saved, this is the place to do it.

Scripting considerations

=> Say what here?

Creating an entry

Creating scripts are different from other trigger scripts in that, since the new entry is being crafted, it is not yet stored within Memento — the Entry object does not yet exist — so it needs to be handled differently.

Use the method defaultEntry(), rather than entry(), to get the DefaultEntry object (the unstored template, if you will, for the eventual Entry object).

Once the default entry is saved, it can be referred to by calling entry() to get the saved Entry object. So Before saving scripts use defaultEntry(), and After saving scripts use entry().

Updating an entry

This event starts when a user has requested that an existing entry be edited (updated) within the library, normally by pressing the Pencil button on the Entry View card. It ends after the entry is resaved to storage — or else not, depending on the processing of the entry.

Phases

This event has three phases. In sequence:

1. Opening an Entry Edit card (synchronous)
The script will be called once the Entry Edit card is open and before any data is displayed.
2. Before saving the entry (synchronous)
The script will be called after the user has pressed the Checkmark icon and before the entry has been saved to storage.
The entered data may be validated; if validation fails, the save may be forestalled and control sent back to the Entry Edit card so the user may fix the errors.
Once the data is validated, this phase is an opportunity for many other functions to be performed to get data to add to the entry to be saved.
The script could be used to update some entry data based on other entry data, to keep it all coordinated.
3. After saving the entry (asynchronous)
The script will be called once the save of the entry has been initiated and will continue in parallel with final processing of the new entry.
If you want to do something whenever an existing entry is updated, but not before the data is saved, this is the place to do it.

The following 2 sections are under development.

Updating a field

TBD

Linking an entry

This event starts when a user has added a linked entry to a Link to Entry field either by selecting an existing entry from the entries list for the linked library or by pressing + (Plus) to create a new entry in the linked library. It ends after the entry is resaved to storage — or else not, depending on the processing of the entry.

Phases

This event has two phases. In sequence:

1. Before saving the entry (synchronous)
The script will be called before the entry has been saved to storage after the user has either selected an entry from the linked library or pressed the Checkmark icon after having added a new entry to the linked library from within the Link to Entry field.
The entered data may be validated; if validation fails, the save may be forestalled and control sent back to the Entry Edit card so the user may fix the errors.
Once the data is validated, this phase is an opportunity for many other functions to be performed to get data to add to the entry to be saved.
The script could be used to update some entry data based on other entry data, to keep it all coordinated.
2. After saving the entry (asynchronous)
The script will be called once the save of the entry has been initiated and will continue in parallel with final processing of the new entry.
If you want to do something whenever an existing entry is updated, but not before the data is saved, this is the place to do it.

The script of this event type has access to additional functions:

masterLib()
Returns the library from which the current record was referenced.
masterEntry()
Returns the entry from which the current record was referenced.
attr(name)
Get the value of the attribute for the current reference.
setAttr(name , value)
Set the value of the attribute for the current reference.

Unlinking an entry

This event starts when a user has requested that an existing entry be edited (updated) within the library, normally by pressing the Pencil button on the Entry View card. It ends after the entry is resaved to storage — or else not, depending on the processing of the entry.

Phases

This event has two phases. In sequence:

1. Before saving the entry (synchronous)
The script will be called after the user has pressed the Checkmark icon and before the entry has been saved to storage.
The entered data may be validated; if validation fails, the save may be forestalled and control sent back to the Entry Edit card so the user may fix the errors.
Once the data is validated, this phase is an opportunity for many other functions to be performed to get data to add to the entry to be saved.
The script could be used to update some entry data based on other entry data, to keep it all coordinated.
2. After saving the entry (asynchronous)
The script will be called once the save of the entry has been initiated and will continue in parallel with final processing of the new entry.
If you want to do something whenever an existing entry is updated, but not before the data is saved, this is the place to do it.

Deleting an entry

This event starts when a user has requested that an existing entry be deleted (actually, moved to the library's Recycle Bin) within the library, normally by pressing the Trash Can button on the Entry View card. It ends after the entry is moved to the Recycle Bin — or else not, depending on the processing of the entry.

Phases

This event has two phases. In sequence:

1. Before deleting the entry (synchronous)
This script will be called after the user has requested the deletion of the entry and pressed Yes on the "Do you really want to?" card, but before taking the action.
Since the user has at this point already confirmed that he or she really wants to delete that entry, what else needs to be done before doing the delete? And what would cause the delete to need to be forestalled? If the delete is to be forestalled, the script could call cancel() and inform the user as to why via message(), and let the user reconsider what to do.
Mainly, help the user reconfirm that the deletion should take place, and if not, call cancel() and inform the user (via message()).
2. After deleting the entry (asynchronous)
This script will be called after the delete operation is initiated and will continue as that operation takes place.
Anything you want to do whenever an entry is deleted should be done in this script.

Opening an Entry View card

This event starts when the user has selected an entry for view and ends after the entry had been displayed.

Phases

This event has two phases. In sequence:

1. Before window display (synchronous)
This script will be called before the entry is displayed. This provides the opportunity to alter or augment the data before it is seen by the user.
2. After window display (asynchronous)
This script will be called after the data display had been initiated, providing the opportunity to perform an action each time an entry had been viewed, such as logging or timestamping.

Adding an entry to Favorites

This event starts when the user has pressed an empty Star icon to make the current entry a Favorite and ends once it has been made a Favorite.

Phases

This event has two phases. In sequence:

1. Before the operation (synchronous)
This script is run just before the entry is made a Favorite.
Data validation could be done; perhaps only certain entries are allowed to be Favorites.
2. After the operation (asynchronous)
This script is called after the entry has been made a Favorite.
This action could be logged, for instance, or it could trigger an action on some other app.

Removing an entry from Favorites

This event starts when the user has pressed a filled Star icon to remove the current entry from Favorites and ends once it has been removed.

Phases

This event has two phases. In sequence:

1. Before the operation (synchronous)
This script is run just before the entry is removed from Favorites.
Confirmation of the user's intent could go here.
2. After the operation (asynchronous)
This script is called after the entry has been removed from Favorites.
This action could be logged, for instance, or it could trigger an action on some other app.


Security

Since the scripts have access to more actions than a user does, they require additional permissions.

The user must define these permissions manually for each library.

To open a card to set permissions for scripts, open the library triggers list and click the Shield icon on the toolbar. Permissions must be set separately on each device. Permissions are not synchronized between devices.

Permissions for scripts

Library permission
determines which other libraries can be affected by the script. You can grant access to all libraries or select only certain libraries. This authorization is required for the libByName() function.
Read permission
grants the script read access to a file
Write permission
grants the script write access to a file
Network
grants to the script the right to execute HTTP requests


Creating a trigger

Each library can have a number of triggers; multiple triggers may exist for each Event type and Phase. To see the list of triggers, open the library, open the menu, and then select Triggers.

To create a trigger, press the 3-dot icon in the upper-right corner of the screen to open the Action Menu; then press Triggers to open the list of existing triggers; then click +. You must then identify the Event type & Phase and write a trigger script that performs the necessary actions.

Writing a Trigger Script

Trigger scripts are in the JavaScript language. See Links to JavaScript documentation below.

Event & Phase
Make sure to understand the Event & Phase you are scripting. This will dictate a number of things, such as whether data is present already or not, whether cancel() makes sense or not, whether the user is waiting for script execution or not, and so on.
Globals get you started
Note the global functions in the sections below; they generally get you started by providing needed information.
Inform the user
Keep the user informed. For instance, if a script is running in a synchronous phase, then a call to cancel() may make sense, but the user won't know what happened and what to do next unless you provide that information, probably via message().
Debug your script
Of course, you can put calls to message("Your message") to help to test your script. To further assist, the log("Your message") global function can be used to send messages to a log without bothering the user about it. By default, the log is directed to the Android developer console; to have it directed instead to a file, go to Memento Settings under Debug and turn on Triggers Logs. You can set the location for the file, but by default, it will be in memento/logs. Both messages from calls to log() and system log messages, including JavaScript exceptions, go into the log.
Permissions
Certain functions require special permissions; read above about that. One such function is libByName(). Others include the file access and HTTP functions.
No return
As of release 4.0.0 of the mobile edition, the script is executed as a top-level script and not as a called function; therefore, for instance, the return statement is not appropriate in a trigger script.

Things to know while writing a trigger script

No implicit context
There is no implicit context for the trigger script, as there is, for instance, in a JavaScript field. Instead, there are global functions, such as lib() and entry() that must be used to set up context for the script.
Entry objects are clones
The Entry object associated with the Event (the one that entry() gives you) is a clone of the actual entry object. If changes are made to this object, they will be saved if the script returns normally. However, if the script calls cancel(), this clone will be discarded upon return from the script, along with any changes that have been made.


See Also

Memento JavaScript Library
Memento JavaScript functions & objects
Trigger Examples
Examples of trigger scripts for common needs
How:Write scripts in JavaScript for Memento
Guidelines for writing JavaScript scripts for Memento