A: Advanced Command Arguments

Scott Waldron Updated by Scott Waldron

Parameter Declarations and the Trestle® Environment

We are going to be jumping ahead a bit here, so don't worry if you don't understand this next section of code completely. All the potential pitfalls will either be covered here, or in one of the sections later that deal with specific "cookbook" type code recipes.

private def _getLastYearLogSelections() {
from System import DateTime;
from Trestle.Data import DbFactory;

var now = DateTime.Today;
var query = string.Format(
$$
SELECT
name as "Name",
id as "Value"
FROM system_log
WHERE date_created >= '{0:yyyy-MM-dd}'
AND date_created < '{1:yyyy-MM-dd}'
$$, DateTime(now.Year, 1, 1), DateTime(now.Year + 1, 1 ,1));

return DbFactory.GetObjects(query);
}

/**
Command function demonstrating selection list with query or records.
*/
def cmdShowLogEntries(strLastYearLog) {
$$
<param name="strLastYearLog"
selections="_getLastYearLogSelections()"
help="Select a log to show the entries for that log record" />
$$
// the namespace shown here should be changed to match the namespace
// of the data classes in your current Trestle application.
from Trestle.Data import SystemLog;

var log = SystemLog.GetRepository().Find(strLastYearLog);
if not log {
return 'Log record not found';
}

return log.SystemLogEntryList;
}

After entering that code, save the script record, then create the following command record (in the Commands tab).

  • Command Name: Show Log Entries
  • Function Name: cmdShowLogEntries(strLastYearLog)
  • Context: List

This code demonstrates the most efficient way to populate a selection list with a database query. Because an HTML <select> tag contains a set of <option> tags, and each of those options has display text (inside the option tags) and a value (an attribute inside the opening option tag), we can use this design feature to show human-readable text in the <select> box, but actually be selecting a different, more useful value, such as a database id.

If we run our new command, then right-click on the Last Year Log drop-down list, and choose Inspect (in the Chrome browser), we can see that (at least in my case) the HTML looks like this:

<select class="custom-select" id="strLastYearLogbeads_17076" name="strLastYearLog">
<option value="103411">Logins 2019-01</option>
<option value="104811">Logins 2019-02</option>
<option value="105211">Logins 2019-03</option>
<option value="107111">Logins 2019-04</option>
<option value="109911">Logins 2019-05</option>
<option value="112111">Logins 2019-06</option>
<option value="113611">Logins 2019-07</option>
<option value="158411">Logins 2019-08</option>
<option value="164811">Logins 2019-09</option>
<option value="290095">Logins 2019-10</option>
<option value="311311">Logins 2019-11</option>
</select>

But how did Trestle know how to create these values.

NameValue Objects and <select>

Whenever you supply a parameter default with a selections="" attribute, what is populated into the drop-down list is translated by Trestle into a list of NameValue objects. The Trestle.NameValue class is exactly what it sounds like: it contains a Name (for us humans to read) and a Value (more suitable for code to understand). Here is the full interface definition that defines the essence of a NameValue object:

/// <summary>
/// A simple Name/Value interface.
/// </summary>
public interface INameValue : IComparable, IComparable<INameValue>
{
/// <summary>
/// The Name of the INameValue.
/// </summary>
string Name
{
get;
}

/// <summary>
/// The Value of the INameValue.
/// </summary>
object Value
{
get;
}

/// <summary>
/// The Value property converted to a string.
/// </summary>
string StringValue
{
get;
}
}

As you can see, the Name of a NameValue will always be a string, and the Value can be any object. As it turns out, this type of object is ideal to translate into an HTML <option> tag, because we can take the Name (the human-readable part) and put it between the <option> tags, and put the Value in the value attribute of the option tag, like this:

// Dino code
var nv = NameValue('Thing', 3);

Becomes HTML option:

<option value="3">Thing</option>

We could construct these ourselves, and that is fine. However, it's also a bit verbose, and instead we can usually rely on Trestle to understand that the intention is to create <option> elements and do the work for us. Here are some Dino functions that will all return a correct list to populate a <select>, along with what the HTML select output would look like.

Populate with Comma-Separated String

The simplest way to populate a select list is to just provide a comma-separated string. If the string is going to be constructed dynamically based on some other environment settings, then it needs to be in a function as shown here. However, if the values are simply hard-coded as this example shows, it makes more sense to just put them directly into the selections="Fast,Cheap,Good" attribute of the parameter declaration element.

def _getSelections() {
return 'Fast,Cheap,Good';
}
<select>
<option value="Fast">Fast</option>
<option value="Cheap">Cheap</option>
...
</select>

Populate with Manually Constructed NameValue List

This method is verbose and there is rarely a need to do it, but it shows how much boilerplate code we can avoid by using one of the other methods described here.

def _getSelections() {
from Trestle import NameValue;

var dictionary = dict[string, string](
'Good' : '1',
'Better' : '2',
'Best' : '3'
);
var selections = list[NameValue]();
each k, v in dictionary {
selections.Add(NameValue(k, v));
}

return selections;
}
<select>
<option value="1">Good</option>
<option value="2">Better</option>
...
</select>

Populate with Dynamic Objects with both Name and Value Properties

This is the method we used for _getLastYearLogSelections. DbFactory.GetObjects() always returns a set of dynamic objects (covered later in Raising a Custom Inform Event on Another Object).

Notice in our SQL query that we returned two columns from a table and renamed them in the query as Name and Value. Harmony is smart enough to look at this list of objects, see that there are appropriate properties, and construct the list of NameValue objects for you. Note that Name and Value are case-sensitive when using this technique! Here are some truncated SQL snippets:

SELECT part_number AS "Name", id AS "Value" FROM product...

SELECT first_name + ' ' + last_name AS "Name", id AS "Value" FROM employee...

SELECT abbreviation AS "Name", id AS "Value" FROM unit...

Note that these dynamic objects do not need to come from a database – they could be manually constructed and that is just fine.

def _getSelections() {
from Trestle import DynamicProxy;

var selections = list[DynamicProxy]();
repeat i 10 {
var d = dynamic();
d.Name = 'Item #' + i;
d.Value = i;
selections.Add(d);
}

return selections;
}
<select>
<option value="0">Item #0</option>
<option value="1">Item #1</option>
...
</select>

Populate with a List of Trestle Records

Here is _getLastYearLogSelections rewritten to use Harmony records instead of a direct SQL query. This can be far more convenient because there is no SQL syntax to remember.

def _getLastYearLogSelectionRecords() {
from System import DateTime;
from Trestle import RepositoryExtensions;
from Trestle.Data import SystemLog;

var now = DateTime.Today;

return SystemLog.GetRepository().FindSearch(
string.Format("DateCreated >= '{0:yyyy-MM-dd}' and DateCreated <
'{1:yyyy-MM-dd}'",
DateTime(now.Year, 1, 1), DateTime(now.Year + 1, 1, 1)));
}

Populate using GetSelectionList Repository Extension Method

Trestle.RepositoryExtensions contains an extension method called GetSelectionList for all repositories that will construct a selection list for you and has parameters for including an empty first selection and whether to order the list alphabetically, and an optional last argument for the search term.

/// <summary>
/// Gets a list of name/values suitable for a selection list.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="self"></param>
/// <param name="includeNull"></param>
/// <param name="sort"></param>
/// <param name="searchTerm"></param>
/// <returns></returns>
public static List<INameValue> GetSelectionList<T>( this IRepository<T> self,
bool includeNull, bool sort, string searchTerm = null )
where T : class, ISelfIdentify
{
return RepositoryExtensionProvider.Current.GetSelectionList(
self, includeNull, sort, searchTerm );
}

To call this from Dino:

def _getFileTypeSelections() {
from Trestle import RepositoryExtensions;
from Trestle.Data import FileType;

// get all records from the file_type table, sorted, with an empty first selection
return FileType.GetRepository().GetSelectionList(true, true);
}

This technique works because each Trestle record is self-identifying, and implements another simple interface called ISelfIdentify. Here is the complete definition of that interface, which has just two methods:

/// <summary>
/// Interface so an object can "self-identify".
/// </summary>
public interface ISelfIdentify
{
/// <summary>
/// Gets the identity. Implementations of this method should not
/// throw an exception.
/// </summary>
/// <returns></returns>
string GetIdentity();

/// <summary>
/// This should be implemented to return true if: 1) both objects
/// are of type ISelfIdentify, 2) the identities are equal, and
/// 3) the type of this object is assignable to the type of other
/// or vice-versa.
/// <para>
/// This should NOT return true for two items of the same identity
/// that are derived from a common base identity object. In other words,
/// if we have a concrete class called Person with id 1, and a Contact
/// that is derived from this with id 1, and an Employee derived from
/// the same Person (with id 1, of course), then the Contact and the
/// Employee should compare true with the Person object, but should
/// compare false with each other.
/// </para>
/// <para>
/// Implementations of this method should not throw an exception.
/// </para>
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
bool IdentityEquals( object other );
}

Multiple Selections

It is also possible for the user to choose multiple selections. When multiple="true" is set in the parameter declaration element, it will cause the selections to render in a dropdown with checkboxes next to the options. Here we will rewrite the log entry display from before to allow the selection of multiple logs and return them as one list.

/**
Command function demonstrating multiple selection.
*/
def cmdShowLogEntriesMultiple(strLastYearLogs) {
$$
<param name="strLastYearLogs"
selections="_getLastYearLogSelections()"
multiple="true"
help="Select logs to show the entries for those log records" />
$$
from Trestle.Data import SystemLog, SystemLogEntry;

var results = list[SystemLogEntry]();
var logIds = strLastYearLogs.Split(`,`);
if logIds.Length == 0 {
return 'Log records not found';
}
each lid in logIds {
var log = SystemLog.GetRepository().Find(lid.Trim());
if log {
results.AddRange(log.SystemLogEntryList);
}
}

return results;
}

When the user runs this command, the strLastYearLogs argument will contain a comma-separated string of the multiple values they selected. We can sometimes use this as-is (for example, as the argument to a SQL IN clause), but often we need to separate out the values and use them separately as we are here.

Although we are changing the parameter to allow multiple selections, we can still call the same function to populate it. After entering this function, add a command record (in the Commands tab) with these properties:

  • Command Name: Show Log Entries from Multiple Logs
  • Function Name: cmdShowLogEntriesMultiple(strLastYearLogs)
  • Context: List

After that is defined and the script is saved, navigate back over to System Log and run the command.

Our argument collection dialog now contains a different control that allows the user to select more than one option. This control is not a <select> at all, but a custom menu showing checkboxes. However, the principle is the same.

Select a few of the options and then press Run to see the results.

How did we do?

A: Dynamic and DynamicProxy

A: Introduction

Contact