Bm 3 – Bill’s mementos, Jul 2023
Beginning JavaScript & Memento objects
For those in the USA, Happy Independence Day on this July 4. This is the 3rd of Bill's mementos newsletters. While I'll continue to number & date the mementos, I will no longer assume the mementos will be published monthly, as the 1st two were. I will work toward publishing monthly mementos, but I make no promises.
The two articles of this memento constitute the beginning of my discussion of JavaScript objects and of the objects provided in Memento. In the 1st of these, I'll discuss objects you may construct yourself and then the math objects of JavaScript that you may use.
In the 2nd article, I'll discuss the scripting of Memento using JavaScript and the most fundamental Memento objects & functions that enable the scripting – The Memento Library & Entry objects.
As always, if you have any question or want something clarified, please reply to this message in the forum so all will read the question and my answer.
Bm article – Jul 2023, Bill’s mementos
Beginning Memento JavaScript, Chapter 3
Objects
Aside: For years, as have many of you, I have used the var keyword to define variables, because when Memento's embedded JavaScript engine (called Rhino) was first developed, it used an old version of JavaScript, and var was the (only) way to define variables. Desktop editions of Memento have used the version of JavaScript installed on their PCs, and usually that's the latest version of JavaScript, so using relatively new JavaScript keywords was no problem, but if you wanted a script to run on both your phone and on your computer, you had to use var.
Well, I've recently discovered that the Rhino people have been slowly updating Rhino, and now there are more modern things available to us in Memento JavaScript than before, even in the mobile edition. Nowadays, while var can still be used, we use let or const to define a variable -- let when the variable is truly to be variable and const when it should never change and thus be constant. So from this point forward, I will use let or const to define variables, but var still works as it always has, if you prefer to use that. There are a few other useful new statements you can use, and I'll cover some of those in future articles.
We’ve talked about literals, like 12 and “12”, and we have talked about primitives, like a literal itself or a variable that contains a literal. But JavaScript is an object-based language. Literals & primitives make basic language features very easy to use. Since JavaScript is object-based though,JavaScript actually (in the background without us having to do anything) makes a primitive into an object before evaluating or calculating with it. This allows object methods (functions within an object) to work on primitives just as they do on objects. So in reality, everything is an object in JavaScript, but JavaScript makes it as easy for you as it can by supporting primitives. An example of this is…
let pi = 3.14159; // variable pi now contains 3.14159
pi = pi.toFixed(2); // pi now contains 3.14
… or you could just say …
let pi = 3.14159.toFixed(2); // pi is now 3.14
…or…
let pi = 3.14159.toFixed(4); // pi is now 3.1416 (it half-adjusts (rounds))
The method toFixed() is one among several instance methods within JavaScript’s Number object and is one of the most often used. So, the variable pi here is treated like a JavaScript Number object. We'll go through built-in objects below. You can look up the things you can do with a Number object, and then you'll know what you can do with numeric primitives like pi, as well. The trick is to generally understand what JavaScript is doing in the background to make things easy for you, so you know what object JavaScript is using for your primitive value.
It is often said that JavaScript is untyped, and that's sort of true, but it still notices the difference between numbers and strings and dates, among some other things. But unlike Memento or some programming languages (like Java), one doesn't declare a variable as having a specific type. Instead of coding (as in Java)...
int one = 1; float real = 1.5; string name = "Bill";
…in JavaScript, one merely codes…
let one = 1, real = 1.5, name = "Bill"; // JavaScript figures out the objects to use
In a literal, when JavaScript sees a digit (or maybe a number starting with a + or -), it knows it might be dealing with a number. If it sees a decimal point, it knows what to do with it, and when it sees quotation marks, it knows it's working with a string instead of a number or anything else.
Definition: An object is a collection of related data and/or functionality in the form of properties & methods. These usually consist of several variables and functions. The most basic object in JavaScript is the Object object. It's this object that is created when an object is created from an object literal, as in the example below.
Let's work through an example to understand what they look like…
let person = {
name: ["Bob", "Smith"],
age: 32,
bio: function() {
message(this.name[0] + “ “ + this.name[1] + “ is “ + this.age + “ years old.”);
},
introduceSelf: function() {
message(“Hi! I'm “ + this.name[0]);
},
};
In this example of an object, name and age are properties of the object -- of the person. They are essentially variables that are hidden inside an object. Then there are 2 methods, bio() and introduceSelf(), which are essentially functions hidden within the object. The name property is an array (like a list) of 2 strings, and age is a number. Methods can have arguments (sometimes called parameters), just as functions do, but neither of these has any arguments. The bio() method builds a biographical sentence about the person and sends it to the user via the Memento message() global function. (It appears for 2 or 3 seconds near the bottom of the screen in the mobile edition and appears in a popped-up bar near the top of the window in the desktop edition. The introduceSelf() method issues a greeting to the user via a similar message.
Instead of types, JavaScript has categories of objects and it has objects instead of types, and JavaScript automatically figures out a primitive's object when it prepares to perform operations with it. That's very convenient, but it is also sometimes mistaken, as we've seen in the forum when a member codes amount + 20 and finds the result to be 123.4520 instead of 143.45, as was expected. JavaScript had originally determined that amount was a string of text characters, so when it encountered the + operator and then 20, it converted 20 into "20" (a String object's value) and, now finding 2 strings and a + operator between them, it concatenated the strings, resulting in another concatenated string.
So, for each category of objects, JavaScript provides one or more objects one can use to work with to eliminate the guesswork, and each of these objects has a number of useful methods one can use to do stuff with such things. Each object also may have properties that can be accessed based on the property's name (some call it a key). JavaScript has many objects within it, but this series of chapters discusses only the basic ones (many of which are analogous to the categories of functions provided with the Calculation field type) plus a few others.
Since JavaScript does this automatically with primitives, what I personally do is use primitives routinely and use object notation only when necessary or convenient, as with the custom person object in the example above, and I recommend you consider doing the same.
You'll likely most often use self-created objects only to create new entries with specified field values, because, in Memento, lib().create() uses object properties as field names and their lproperty values as field values. Otherwise, you'll likely find little need to deal with objects of your own creation.
Since Memento's create() uses properties as fields, the names of those properties will often include spaces and maybe other characters that are not normally proper in property names, rather than using the normal JavaScript dot notation (like person.name), use the JavaScript bracket notation (like person["name"]). Then you could change the first field name to "first name", and it would still work (person["first name"]); in this case, dot notation wouldn’t work.
In Memento, the main case where a literal object is used in in Memento’s Library object for the create() method. You create an object whose properties are each field’s names & values, and when you call create() on the library you’re wanting to create an entry in, it used those properties & their values to create a corresponding entry in the library. For instance…
If the variable journal contains the Library object, and you want to create a new entry in there, and if the fields in that library are Customer name (text), Date (date), Item (single-choice list), Quantity (integer), and Unit Price (real or currency), you’d do the following…
let sale = {
Customer name: "John Smith",
Date: new Date().getTime(),
Item: "Screw",
Quantity: 3,
“Unit Price”: 1.25
}
Then…
journal.create(sale); // Actually create the entry
Now let's start with the math category, which contains the Number object and the Math object. The explicit object way of getting x as a Number is…
let x = new Number; // Equivalent to let x or var x, but an explicit object
Let's initialize y…
let y = new Number(1.23456); // Equivalent to let y = 1.23456
Either way, you can immediately start using x or y as numbers (x = y) or as objects (x = y.toFixed(1); // Now x = 1.2 & y = 1.23456).
For those interested in doing general math with numbers in JavaScript, here are JavaScript's Number & Math objects and their methods.
Here are examples of numbers created using the Number() object constructor function using an argument of a string. You wouldn't normally do it this way; this for illustration. JavaScript will convert the argument to a Number object automatically.
Number("123"); // 123 (converted to numeric 123)
Number(123) === 123; // true (same type (number), same value)
Number(12.3); // 12.3
Number(12.00); // 12 (Whole numbers are called fixed, but not a different type)
Number(123e-1); // 12.3 (exponential/scientific notation)
Number(""); // 0
Number(null); // 0
Number(0x11); /* Hexadecimal (base 16). In decimal (base 10), 1*16 + 1 == 17. Hex digits are numbered 0 through F (which is 15 in decimal), 0123456789ABCDEF */
Number(0b11); // 3 (binary (base 2), digits are bits, 0 or 1)
Number(0o11); // 1 * 8 + 1 = 9 (octal (base 8), digits are 0-7)
Number("foo"); // NaN (Not a Number)
Number("100a"); // NaN
Number(-Infinity); // -Infinity (special global constant number)
Number(#) // Constructor
To make a new Number object, type one of the following…
let n = new Number; // n is a Number object that has no value (yet)
let n = new Number(); // n is a Number object that has no value (yet)
let n = new Number(23); // n is a Number object with the (initial) value of 23
Examples: let x = Number(123);
toFixed(#)
To set a Number value to a specific number of decimal points, type one of the following…
n = n.toFixed(2); // Round the value of n to 2 decimal digits of precision
n = n.toFixed(); // Round the value of n to an integer value (same as n.toFixed(0))
toString()
To put the value of a Number object into a String object, type the following…
let s = n.toString(); // If n is 12.34, s is “12.34”
The Math object provides a wealth of methods that you might want to use with numbers, many of which you may be accustomed to using with Calculation fields. They are static in nature, so to use them, you code Math.something rather than using a variable to contain a reference to a specific number and a Number object method.
as follows: var num = Math.abs(-3); // Math.abs() returns the absolute value of the value in num, which will be +3.
There are more static properties, but they are seldom used.
Example: let arctangent = Math.atan2(Math.PI, Math.PI / 2); // See atan2 below.
There are a few more Math methods, but they are less often used, like the hyperbolic trigonometric functions. If there is interest, I could document those, as well, but you can look them up on the Web.
Another JavaScript math object we can discuss in the future is the BigInt object, which is intended to handle extremely large integers. Since the Number object handles very large numbers itself, you likely will have no need for the BigInt object. If you think you might need it, you can look it up by that name or contact me.
We will soon be discussing other built-in objects, such as the String object and the Date object. Please stay tuned for that. The Logical category of functions supported by the Calculation field is not needed in Memento JavaScript, as JavaScript has those capabilities built into the language itself, so no functions or methods are needed for logical operations.
Bm article – Jul 2023, Bill’s mementos
Beginning Memento JavaScript, Chapter 1
Library & Entry objects
In parallel with explaining objects of the JavaScript language, I want to explain the additional objects provided by Memento for use in scripting databases. There are 2 primary such objects whose use makes up 95% of the use of Memento scripting – the Library object & the Entry object. This article will explain those.
There are only another half dozen or so other Memento JavaScript objects, and those will be explained in a subsequent article in a future memento. They have to do with ancillary things that can be done in Memento JavaScript.
As you know from reading my previous articles, an object is a container for data (properties & their values, like variables & their values) and behavior (methods, like functions). That's all they are, so there's no further mystery. Just like you can make a variable to contain a price (let price = 1.99;), you can decide to make it an object and keep more info…
let product = {
name: "nail file",
price = 1.99,
salePrice = 1.99
}
If you want the ability for a product to be discounted, you could add a method for that, so the object grows to become…
let product = { // info…
name: "nail file", // property
price: 1.99, // property
salePrice: 1.99, // property
discount(percent) { // behavior…
this.salePrice = (this.price * (1 - (percent / 100); // method
}
}
So, an object contains the info & behavior that defines the object.
In the Memento wiki, in the past, I've written a couple of tutorial pages about scripting Memento. They are…
https://wiki.mementodatabase.com/index.php?title=How:Write_scripts_in_JavaScript_for_Memento
https://wiki.mementodatabase.com/index.php?title=Tips:Using_JavaScript_in_Memento
As reference material, you can read 4 other pages I wrote regarding…
https://wiki.mementodatabase.com/index.php?title=Trigger_Examples
https://wiki.mementodatabase.com/index.php?title=Data_Sources
In this article, as in all Bill’s mementos articles, I'm assuming no previous knowledge whatever of scripting or programming, so this could be easier for you to comprehend.
As of this writing, if you want to add scripting to your database, you have in each library 4 choices…
Add an action (reference #1 above)
Add a library action when you want a button on the entries list screen within a library and when pressed have a script run that likely pertains to all entries or the library as a whole.
Often, a library action script will access all the entries in a library and process them in some way one-by-one, maybe calculating a total of the values of some field.
Add an entry action when you want a button in an entry view screen and when pressed have a script run that likely pertains only to that entry.
Often, an entry action will operate on the current entry or on those to which the entry may be linked.
Add a trigger (reference #2 & #3 above)
Triggers may be triggered at various places in your process of working with a library or an entry. In reference #2 above, there is a table of events and phases identifying when & where these triggers should take place. If you want a script to run when any of these events & phases take place, set up a trigger of that type and put your script into the script editor. Don't forget to check reference #3 above for examples.
Triggers for events associated with a library, like upon opening the library, are called library triggers.
Generally, a library trigger will establish some default value(s) for some field(s) or access the most recent previously saved entry and use it to establish a field value.
Triggers for events associated with an entry, like saving the current entry, are called entry triggers.
Usually, an entry trigger will perform something related to the current entry just before or after saving the entry or upon opening the entry in an entry edit card.
Add a custom data source (reference #4 above)
A data source is used by the Autofill feature in the Edit Library screen and specifies the source of the data to be used to populate the fields of an entry. The source is frequently one of the provided data sources for things like books, music, movies, and so on, but a custom data source can be scripted to source the data from other Memento libraries or files or wherever.
Add a Button field (No reference above, read below)
Subsequent to my retirement from active work on the Memento wiki, some new field types have been released by the developer, and one of these is a Button field. A Button field is essentially identical to an entry action except that, instead of the user pressing a Play button at the top of the entry view card, the user presses a labeled button in a field within the entry. There is no hyperlink reference here to the wiki for this field type because a page for this field type has not been created in the wiki (I retired). However, since it behaves just like an entry action, using the same objects & methods as an entry action, you can just write the script as if it were an entry action, but the script is placed within the Button field definition within Edit Library.
All the scripting discussed in this article implicitly uses the Memento JavaScript Library (the MJL). A reference for this library (or API) is located at…
https://wiki.mementodatabase.com/index.php?title=Memento_JavaScript_Library
If you've scripted in the past using a JavaScript field, note that there are a few differences between a JavaScript field script and an MJL script, like those covered in this article. Those differences are…
A JavaScript field implicitly works within an entry. To keep things as simple as possible for JavaScript field scripters, Memento makes an assumption that references to functions like field() refer to the current entry. For instance, let price = field("Price");.
However, in MJL scripts, the scripter specifies the object explicitly rather than assuming the current object. If you want to refer to the current object, all that needs to be done is to obtain the current object (like let e = entry();) and use it when calling methods like field() (like let price = e.field("Price");).
Scripts for JavaScript fields can reference entries in libraries other than the current library only by accessing them through link-to-entry fields. Otherwise, access is only to fields within the current entry.
JavaScript field scripts return values, whereas MJL scripts might do almost anything, like setting values using objects & methods that are in the MJL.
The Memento Library object refers to a Memento library, as defined in Edit Library. See the following wiki page for details…
https://wiki.mementodatabase.com/index.php?title=Memento_JavaScript_Library#Object_Library
Library objects have only two properties. They both have the same value, but each has its own name. If you have a library object in a variable called lib, you can reference the library's name in two ways – lib.name or lib.title. (For years, a library name and an entry name was referred to as a name. That seems logical. Around the time the desktop edition was first released, the word title started to be used for some reason. Now, we can use either, but I still use name. Why not? Library names and entry names are still called names.)
But the Library object does have a number of methods (functions within the object). Probably the most commonly used method of these is entries(); you call it by writing let ents = lib.entries();. You pass it no arguments; you just call it, and it returns to you all the entries in the entire library into an array (essentially a list) that can be readily indexed in memory. Most library action scripts include a for loop that goes through the entries in this array one-by-one.
You can use the Library object's create() method to create new entries. The technique is to create a new object, add a property for each field (The property name is the field name. The property value is the field's value.) You then call lib.create(), passing it the new object you just created. So, if the new object is called obj, you would call lib.create(obj).
The linksTo() method allows you to get a list (a JavaScript array) of the entries (Entry objects) in various libraries that are currently linking to the entry you ask about when you call it. So, if you have an Entry object called ent, then linksTo(ent) will return that list of entries to you, and you can go through them one-by-one, if you like.
There are 3 find methods: find(), findById(), and findByKey(). To search all library field values, use find(). To search only the entry names, use findByKey(). If you have an ID from an ID field (column in Google Sheets, if synced), you can search for that using findById().
There is also a method fields() that returns a list (a JavaScript array, actually) of the names of all the fields on the MAIN page of a library. Only those on the MAIN page are available using fields(), which limits its usefulness considerably.
The Memento Entry object defines a library entry. See the following wiki page for details…
https://wiki.mementodatabase.com/index.php?title=Memento_JavaScript_Library#Object_Entry
Entry objects have the following properties…
The Entry object has the following methods. See the following wiki page for details…
https://wiki.mementodatabase.com/index.php?title=Memento_JavaScript_Library#Object_Entry
The most often-used Entry object method is field(). It's used to get the value of a field. If the variable containing the Entry object is called e, then to get the value of the Date field of that entry into a variable called date, you'd write let date = e.field("Date");.
The value of each Memento field is returned to the scripter based on its type. See the field() method under Entry object for a table defining the way each field type is interpreted and the kind of value it returns. The most recently released field types might not be found in that table, since I'm now retired and haven't been updating it.
By now, it should be apparent that variables, properties, and Memento fields are very similar to each other. A variable is merely something in JavaScript with a name and a value. A property is something with a name and a value within a JavaScript object. And a field is something with a name and a value within a Memento entry, as defined in a Memento library.
It should also be apparent by now that to get a list of things in JavaScript, a JavaScript array is used, and to access the 1st element in an array named things, you refer to things[0] and for the 3rd element, you reference things[2]. I'll cover arrays in the next chapter of the Beginning JavaScript series in a memento soon to come. There, you will learn that you can get the number of elements in an array called things by referring to the things.length property – in this case, the length is 3.
Going through the other Entry object methods, the link() method adds a link to the array of links from a link-to-entry field to the specified entry.
The recalc() method causes the values of all the entry's fields to be recalculated. So it's e.recalc();.
The trash() method causes the deleted property to be set for the entry. This effectively removes the entry from the library, but the Library object's find methods may ignore this property and include them in results. So, it's e.trash();.
The untrash() method causes the deleted property to be reset, thus effectively returning the entry to the library from the entry recycle bin. So, it's e.untrash();.
A frequently used method is the set() method, which sets the value of the field whose name you give to the value in the 2nd argument. So, it's e.set("Discount", 0);.
The show() method causes the entry view card for the entry to be displayed. So, it's e.show();.
The unlink() method results in the removal of the entry from the array of links in the specified link-to-entry field. So, to have entry ent removed from the lte link-to-entry field in entry e, it's e.unlink(lte, ent);.
Remember that for examples of triggers scripts, see…
https://wiki.mementodatabase.com/index.php?title=Trigger_Examples
What follows is some explanation of the general approach to using Library & Entry objects to script Memento…
Usually in action scripts, but also occasionally in trigger scripts or Button field scripts, one might want to sift through the library entries to either access or do something to them all or else identify certain ones and access or do something only to those entries. The skeleton code to go through all the entries is…
let lib = lib(); // Or libByName(“Other library”);
let entries = lib.entries();
// Now all the entries are in the array called entries
// Now we can loop through the entries in the array
// using the for..in form of looping…
for (ex in entries) { // ex is an index to each entry in the entries array
let e = entries[ex]; // Now we have the entry itself
// Fields can now be referenced with e.field(fieldName)
// to do what you want. To set a field value, use e.set(fieldName).
e.set("Total price", e.field("Unit price") * e.field("Quantity");
}
An alternate way to do the for loop…
for (ex = 0; ex < entries.length; ex++) {
let e = entries[ex]; // Now we have the entry itself
// Do what you like with entry e
}
To operate on only some entries, use an if statement within the for loop to identify those fields…
if (e.field("Type") == "Debit") { // For example
// Do your e.set() or other statements here
}
Another alternative is to use find(), findByKey(), or findById() to locate the entry or entries you want to work with…
Using find() is similar to the above, since it also returns an array of entries…
let lib = lib();
let entries = lib.find("query");
// Now, process this subset of the library's entries
// in similar fashion to the above using a for loop.
Using findByKey() or findById() is similar except that only one entry is returned, so there need to be no loop. For this to work, the key or entry name must be unique within the library…
let lib = lib(); // Or libByName(“Other library”);
let serialNumber = "0182QX"; // For example, or other unique value
let e = findByKey(serialNumber);
if (e == null) {
message("No item found"); // Or some other way of announcing a message
exit(); // Stops running the script
}
else {
// Now do what you like with the fields of the entry and/or of linked entries
}