This article is accompanied by two sample forms to help you learn: 1.) New list and item functions (to be deployed with this CSV pre-load file), and 2.) Other new functions. For the full documentation on all of the functions available, check out our documentation on Using expressions in your form.
SurveyCTO 2.70 has a lot of exciting new features, including field plug-ins and server console branding (learn more in our blog post about the release). But, 2.70 also has a series of excellent new functions for use in expressions that can greatly improve your form design workflow.
New list and item functions
Before 2.70, if you wanted to work with and retrieve data from lists, they would often have to be stored in space-separated. This was fine in most cases, but what if the items in the lists included spaces? For example, what if you were storing the full names (both first and last names) of household members? Now, with the new functions, you can use any separator you'd like in lists!
The old list functions will still work perfectly well, so there is no need to re-design old forms with these new functions. These functions offer new and improved options for performing calculations and data manipulations in fewer steps. |
For example, let's say a household has these family members:
- Abraham Van Helsing
- Ali Baba
- Baital Pachisi
- Dorothy Gale
- Loch Ness Monster
- Sherlock Holmes
- Snow White
- Tom Sawyer
- Victor Frankenstein
- Vlad Dracula
Historically, an important method for dealing with lists has involved joining values into space separated lists. However, you cannot put them into a space-separated list, since the spaces in the names are important, and it will make it look like there are 22 items (or names) instead of 10. However, you can put them into a list where each name is separated by a comma-space ,
(or whatever separator you prefer), and then use the new functions to retrieve each item. Here is our list, stored in a field called "list":
Abraham Van Helsing, Ali Baba, Baital Pachisi, Dorothy Gale, Loch Ness Monster, Sherlock Holmes, Snow White, Tom Sawyer, Victor Frankenstein, Vlad Dracula.
Here are the new list and item functions:
count-items()
If we wanted to count how many names are in that list, it used to be that the list had to be space-separated. Now, with the new count-items() function, we can specify ,
as the list separator, like this (between the single quotes in the first parameter):
count-items(', ', ${list})
That expression will return 10, since there are ten items in that comma-space-separated list (not 22 items).
count-items() is similar to the pre-existing count-selected() function.
item-present()
If we wanted to know if a certain item is present in our comma-separated list, then we can use the new item-present() function. For example:
if(item-present(', ', ${list}, 'Sherlock Holmes'), 'Yes', 'No')
This example will return "Yes", since "Sherlock Holmes" is present in that list.
item-present() is similar to the pre-existing selected() function.
Note: the if() function doesn't need to be used with item-present() in practice. By itself it will return TRUE (a value of 1) or FALSE (value of 0). The if() function provides a simple way to return a more user-friendly result.
item-at()
If we wanted to know which item is at a certain position in the list, we can use the item-at() function. The first item will be at index 0, the second item will be at index 1, and so on. For example, if we wanted the second family member in the list, we would use the expression:
item-at(', ', ${list}, 1)
In our list, that would return "Ali Baba".
item-at() is similar to the pre-existing selected-at() function.
For an advanced example of how useful item-at() can be, see this sample form, where a complex string returned by a barcode (from an Aadhaar card) can be parsed in a few steps. |
item-index()
We can also do the opposite of item-at(), where if we have the name (or value) of an item, we can check which position that item has in the list with the item-index() function. For example, if we wanted to know the position of "Loch Ness Monster" in the list, we would use this expression:
item-index(', ', ${list}, 'Loch Ness Monster')
Since the index starts at 0, that expression would return 4. If "Loch Ness Monster" was not in that list, then it would return -1.
item-index() has no similar pre-existing function.
Hint: item-index() can be used with a list created from repeat groups using the join() function, or with the new join-if() function (see below), as part of a new way to find the index value of a repeat group instance for returning values from other fields in that same repeat group instance. For example, if you use item-index(', ', ${list}, 'Loch Ness Monster')
to find the position of 'Loch Ness Monster', and store that value in the field "lnm_pos", you can use indexed-repeat(${mem_gender}, ${family_repeat}, ${lnm_pos} + 1)
to find the gender of 'Loch Ness Monster', even if the list is in a different order.
New if() functions
In addition to new list functions, we have a series of new functions for repeated fields that allow them to be conditional. For example, what if you wanted to use the join() function to create a new comma-separated list to use with the new list functions, but you want that list to only include items that meet certain criteria? Our new functions can help you with that!
join-if()
Let's say that the "list" field from before was created by using the join() function on a repeated field called "mem_name", like this:
join(', ', ${mem_name})
Now, let's say that the repeat group also has a field called "mem_gender" that has the value of 1 if the family member is male, and 2 if they are female. Using the new join-if() function, you can create a new list that only includes the female members, like this:
join-if(', ', ${fam_mem_name}, ${mem_gender} = 2)
That will return a comma-space-separated list of all family members that are female:
Dorothy Gale, Loch Ness Monster, Snow White
The fields in the condition can be any fields that are in the same repeat group as the field that is being joined, at least as long as they are not inside a repeat group within the repeat groups that the field to be joined is located.
min-if(), max-if(), count-if(), and sum-if()
Just like their cousins the min(), max(), count(), and sum() functions, these functions return the minimum, maximum, and sum of values of a repeated field. And also, just like the join-if() function, you can set it so only certain repeat instances are checked and included. For example, in a repeat group in the sample form, you can enter a series of test scores and whether that score received help or not. You can then use the min-if(), max-if(), count-if(), and sum-if() functions to find the minimum, maximum, count, and sum of the scores only where the respondent did not receive help, like this:
min-if(${test_score}, selected(${test_help}, '0'))
max-if(${test_score}, selected(${test_help}, '0'))
count-if(${test_score}, selected(${test_help}, '0'))
sum-if(${test_score}, selected(${test_help}, '0'))
Just like the join-if() function, the fields in the condition can be any fields that are in the same repeat group as the field that is being checked for min/max or summed, at least as long as they are not inside a repeat group within the repeat group that the field being evaluated is located.
rank-index-if()
Just like the rank-index() function, this function returns the rank of a specified instance of a specified repeated field, and ranks it compared to the other instances of that field. But with the rank-index-if() function, it only takes into account repeat instances that meet a condition. For example, using the situation above, if you wanted to check the rank of the fifth test score among the test scores where the respondent did not receive help, you can use this expression:
rank-if(5, ${test_score}, selected(${test_help}, '0'))
Other new functions
There are also many other new functions you can use in your forms.
choice-label()
Functionally, this is the same as the old jr:choice-name() function, but the choice-label() has a more intuitive name and more intuitive syntax. The first parameter is the name of the field with the choice list, and the second is the value of the choice you would like the label for. For example, if the field with the choice list is called "selectone", and you would like the label of the choice with the value of 1, you would use this expression:
choice-label(${selectone}, 1)
If you would like the label of the choice selected for that field, you would use the expression:
choice-label(${selectone}, ${selectone})
Besides the name of the function, the only differences between this and the jr:choice-name() function is that the order of the parameters is different, and the single quotes are no longer necessary.
2.70 also has an update where choice labels can now be retrieved even when dynamically filtered choices have not yet been shown to the user, and this has been applied to both the choice-label() and jr:choice-name() functions. Check out the release notes for further reading. |
decimal-date-time() and decimal-time()
With the decimal-date-time() function, you can calculate exactly how many days have passed since January 1, 1970. This has many uses, such as subtracting two decimal-date-time() values to determine exactly how many days have passed between two date-times, even down to the fraction of days:
decimal-date-time(${later_date}) - decimal-date-time(${earlier_date})
You can also use it to determine what the date will be a certain number of days in the future. For example, you can use this expression to determine what the date will be in five weeks:
date(decimal-date-time(today()) + 35)
That way, no matter if this month has 30, 31, or even 28 or 29 days, it will determine the exact date in five weeks.
The decimal-time() function is very similar, except it only returns the value after the decimal point. For example, both decimal-time('12:00') and decimal-time(' 2020-01-01 12:00') will return 0.5, since 12:00 is halfway through the day. This has several uses, such as being used in a constraint to verify a time entered is before or after a certain time, ensuring a form is being completed at a certain time, and many others.
coalesce()
While not new, the coalesce() function has been improved, allowing it to take as many parameters as you'd like. For example, what if you prefer to reach out to respondents via email, but you can't always collect a respondent's email address? You can have a series of fields that ask for contact information, and then use the new coalesce() function to return the first one that has a value, in order of preference:
coalesce(${email}, ${phone_num}, ${home_address})
That way, if "email" has a value, then you will use that to contact the respondent, but if not, then you will use their phone number, but if that was not given either, then you will use their home address. The expression above just has three parameters, but you can add as many as you need.
You can even use it to determine if all values are blank. If the expression above returned no value (or 'null'), then it meant that you won't have any contact information, and you can use that to display an error message that none of the contact information was filled in. For example, if the above expression is used as the calculation expression for a field called "coalesce", you can create a field with a warning that has this relevance expression, so it only displays if all fields are blank:
empty(${coalesce})
Speaking of empty()...
empty() and relevant()
The empty() function tests whether a field is empty. A field that has no value or is not relevant is empty. The empty() function is a new, convenient function-driven option to establish whether a field was empty. This was possible before though; to exemplify, all three of these functions are equivalent:
empty(${fieldname})
${fieldname} = null
string-length(${fieldname}) = 0
Hint: empty() can also be used with not(), to express "not empty" (e.g. not(empty(${fieldname}))
).
The relevant() function takes this a step further. This checks if a field was actually relevant or not based on its relevance expression or the relevance expression of its group. For example, what if there is a field (we'll call it "field2") that asks a question about another field (we'll call it "field1"), but "field1" has a complex relevance expression you don't want to deal with again. You can give "field2" this relevance expression so it only appears when "field1" is relevant:
relevant(${field1})
geo-scatter()
Let's say you would like to publish the approximate location of your respondents, but to respect their privacy, you don't want to publish their exact location. The geo-scatter() function is a great way to accomplish this! For example, let's say you have an encrypted form, and an enumerator has retrieved the exact location of the respondent in a field called "location". There can then be a publishable field with this calculation:
geo-scatter(${location}, 1000)
That will return a random location somewhere within 1,000 meters (1 kilometer) of the original location so you don't have to publish the actual location of the respondent.
Hint: New GPS points generated by the geo-scatter() function are truly random, so use a suitably large RANGE value for the maximum distance from the original point with it in mind that some random points will be closer to the source than others. Also, remember that the pre-existing short-geopoint() expression can be useful for processing GPS points for publishing, depending on what mapping software you'll use.
upper() and lower()
We'll end on a couple easy ones. The upper() function takes text, and returns the text as all uppercase, and the lower() function takes text, and returns the text as all lowercase. For example, let's say the field "phrase" has this value:
The quick brown fox jumps over the lazy dog.
If you were to put that into the upper() function like this:
upper(${phrase})
It would return:
THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.
If you were to put it into the lower() function:
lower(${phrase})
It would return:
the quick brown fox jumps over the lazy dog.
Hint: You can use upper() and lower() to standardize and format text field inputs from form users, to help promote consistency (e.g. when capturing unique ID's manually).
As stated above, consult the product documentation on using expressions in your forms for the most authoritative record of how each function is used.
Do you have thoughts on this support article? We'd love to hear them! Feel free to fill out this feedback form.
0 Comments