If you’ve been using the OutSystems platform for a while, then you are probably familiar with the “historical” list actions found in the (System) component:
- ListInsert a
I won’t describe what these venerable actions do here. For the most part, their names are pretty self-explanatory, but if you can get all the details from the official documentation.
The New Kids on the Block
What many people haven’t realized yet is that OutSystems 10 offers quite a few more, all available for mobile and web.
Let’s focus on these list operations:
- ListAll Action: Returns true if all the elements in the list satisfy the given condition.
- ListAny Action: Returns true if any element in the list satisfies the given condition.
- ListFilter Action: Returns a new list with the elements of the source list that satisfy the given condition.
- ListIndexOf Action; Returns the position of the first element that satisfies the given condition or -1 if no element was found.
- ListSort Action: Sorts the elements in the list by the given criteria.
Looking at the very brief descriptions, it’s immediately apparent these are all useful in their own right. Not only that, there are clear advantages in using some of these even if you could code equivalent functions.
Let’s delve deeper by focusing on ListFilter.
There are three important points about ListFilter that you need to know.
#1. Cleanliness Is Next to Godliness
The first point is the tidiness of the code. For example, if you want to create a subset of a list of products with “Philips” in their descriptions (if this doesn’t get me a free toaster, nothing will) you could use:
… even better!
Beyond the obvious simplicity of fewer nodes and links in the ListFilter approach, you can also see that:
- The scope of the condition property is relative to the object in the SourceList. You are checking description only, not Products.Current.Description. If your filter condition involves many attributes, this makes a big difference in readability.
- You don’t need to litter your scope with an additional ProductsResultvariable to hold the filtered list; the output variable PhilipsProducts.FilteredList has the filtered result.
Of course, it can be argued that moving the cycle to a user action produces the same simplicity. This is true, but we’re not done yet!
#2. Faster Than a Speeding Bullet
The fact of the matter is that the filter approach is faster. Much faster.
I filtered a products list (with 100k products) approximately 200 times, using both approaches, and the ListFilter action consistently outperformed the iterator by a factor of 2.5x.
Of course, if you are fetching the products from the database, you should absolutely filter using an Aggregate filter (or a SQL query WHERE clause) and let the database do the work, But, if you cannot do this, ListFilter is the way to go.
#3. More Powerful Than a Locomotive
A whole world opens up for you when you keep in mind that the condition does not have to involve just the attributes of the record in the SourceList and that you can use things also in scope very creatively.
Paginating a List
Now it’s time to paginate the products list with a number of items per page of PageSize. To get the items for the PageNumber of each page (the first page’s number is zero, BTW), you can again use ListFilter this time with this condition:
Products.CurrentRowNumber >= PageSize*PageNumber and Products.CurrentRowNumber < PageSize*(PageNumber+1)
This uses the fact that the source Record List is being internally iterated as ListFilter is being executed, so the condition — and therefore CurrentRowNumber — is re-evaluated for every item. The rows that “make it” to the result list are the ones that go from the first to the last index in your page (pages start at zero).
A shout out to Carlos Alfaro for sharing this cool example.
Getting the “Unique” Values of a List
Assuming the same products list has an attribute with the item’s category, you can get all the unique categories in the list. Simply use ListFilter with a condition of:
Products.CurrentRowNumber = 0 or Products.Current.Category <> Products[Products.CurrentRowNumber-1].Category
For this to work, it’s presumed that the list is sorted by category, but if that’s not the case, call ListSort first.
Again, this uses the same CurrentRowNumber re-evaluation trick. The first category in the list is, by definition, one you haven’t seen yet. And, since the list is sorted by category, so will be a category that is different from the one on the previous line. Easy!
Filtering Based on Another Record List
If you have two lists where, row by row, the information in one is related to the other, you can mash up CurrentRowNumber changing and using [ ] to index so you can “peek” into the secondary list to see if the row on your main list should be in the result set or not.
Imagine you have the list of products and a parallel list called Selected with an IsSelected attribute. You can obtain only the products selected with ListFilter and the condition:
To Infinity and Beyond
These are just a few examples of how you can “think outside the box” with list operations. Although we focused on ListFilter, these techniques can be applied to some of the other list functions.
While ListSort seems to be restricted to a static attribute — despite the “By” property being a text expression that is executed a whopping 10x per line (!) — you can readily adapt the “unique values” tip with ListAll to ensure all elements are different, or that they change in a deterministic way as you go through the list, for example.
In fact, if you let your imagination run rampant, I’m sure you’ll come up with even better examples.
Found a cool one? I’d love to hear about it on Twitter!