Definition
A Seeker is a special ViewModel for searching and finding things in the database.
Introduction to SeekLogic
To get anything done, you need to find the necessary things. A typical software system has the same need. How do we go about declaring a non-limiting, multi-variable, user-friendly seeker into a generic model-driven system like the ones we build with MDriven?
This is what we need:
- We need unrooted (i.e. not having anything to start with) persistent storage evaluated OCL expressions to execute a search.
- We need user input on how to limit the search.
- We need to allow the user to use different limiting criteria as he or she sees fit; after all, the One-Field-Matches-Everything tactic that Google uses does not cut the mustard in enterprise applications. Users will want to limit the search to “Only this department”, “Only things from yesterday” etc, and even in Google, you need to use an extended syntax to get this done.
- We need to allow multiple sets of search definitions per seeker interface. If the user does not get a hit using the filters with the first set, it is natural for the user to “try again”. Then, we want to use another set of search criteria; this has been tested on real users and many find it intuitive and obvious that it should work this way.
Consider this model:
This is how we can define a Seeker:
#1 We declare variables that hold the user input – one variable is a string and the other is an Object reference of type ReferenceClass (from our model).
#2 We add Columns that use these variables so that we get some UI for the user to enter the criteria into. The Reference value we set up as a PickList. We create a SearchExpression - right-click menu on the ViewModelColumn - Add nested – Add search expr. When adding the first search expression to the ViewModel, the Designer will also add the default implementation details with a vSeekerResult variable and a SeekString. This is intended to help - there is nothing magical about the added widgets. The only important thing in a Seeker is that there is a variable named vSeekerResult, which is of type collection.
#3 Add two criteria. The result of the two criteria will be intersected (on the server side). Here is an important fact: Since the result of the criteria will be intersected, we need some way to say if a criterion is Active or not – after all, it is up to the user to limit the search on either or both of the two criteria.
This is how the activation of a Criteria is done:
and
The Active Expression is optional. If you leave it blank, it defaults to true – always on.
#4 The second batch of search expressions is executed the second time the user clicks the search button BUT only if the search variables have not changed. You can have as many search batches as you need, and they are Round-robin-used whenever the user clicks the search button with untouched variables. Whenever a variable is changed, the Round-robin is reset and the first batch is used again.
Even if these rules may seem complex, they are intuitive for the user – especially if you use the search batches to filter for the same data as the resulting columns show. For example, the user enters someone’s first name, but your first batch filters on the last name – the wrong people come up for the first search, and the user hits search again. Now we use the second batch where you filter on the first name – voila! This was just an example. You can create a filter expression that unions the first name and the last name results.
Person.allinstances->select(a|a.FirstName.SqlLike(vSeekString+’%’))->union(Person.allinstances- >select(a|a.LastName.SqlLike(vSeekString+’%’)))
Another example might be that the user enters a number. First, we try to match it with a product code, the user hits search again, and we try to match it with the order number. The user is still not happy so he hits search again. Now we match it with the phone number of the customer – the user is happy. In this example, we could have chosen to create a detailed search interface with 3 text boxes – one for the product code, one for the order number, and one for the customer phone number – just as valid. Or we could do a union expression as above – just as valid. Choose the strategy that sits best with your users, but I urge you to test the simple interface with a single or only a few input boxes - it is user-friendly.
Databases Use SQL
The search expressions for OCL criteria are different from those of other ViewModel column expressions in one important aspect: they are executed against persistent storage (your database). If your database is an SQL-Server, these expressions will be translated to SQL and sent to the database for evaluation and execution. The database is much better and faster at handling huge volumes of data than MDriven. This means that we can filter out specific objects in our model from a nearly unlimited-sized database. The multi-variable seekers described in this chapter will be the natural starting point for your users' work in your system. They search – they find – and then they work. The work part is often rooted in a specific found object where your other ViewModels expand the neighboring information.
Useful Search Examples
Dates
Dates are a little tricky because SQL servers encode dates in many formats. We need to pass dates to search for, not as text, but as values of the date type.
So, when filtering on dates, use variables of the type DateTime.
We've created a variable vAfterDate here and are using it to search.
Person.allInstances->select(p|p.Registrered>=vAfterDate)->orderBy(p|p.Registrered)
This will find all Persons registered after the given date and order them on the registered date.
Need to filter on dates without the user seeing it? (To limit the search result maybe?) Set the DateTime variable before selfVM.Search on the search button like this:
vAfterDate:= DateTime.Today.addMonths(-6); selfVM.Search
Limiting the Number of Results
By adding the tagged value MaxFetch, you can change the number of records shown before asking the user to extend their search. Also, see SeekMore logic with paging Search_result_pages.
What Type is Searched - What Type is vSeekerResult?
The vSeekerResult will be created for you as a collection of RootType of the ViewModel. If you want another type of vSeekerResult, you can declare the vSeekerResult as Collection(YourDesiredType), and then seeker logic will assume the search type is YourDesiredType.
Order, OrderBy, OrderDescending in Seeker - Extensions added 2021-01-16
Since the MultiVariable seeker may combine expressions with ->intersection, it has not been very easy to get a good "order by" expression to work consistently over the many possible combinations of final expressions used.
Furthermore, OclPS has been limited to using only the attributes on the main class for order by.
To remedy these 2 challenges, we have extended OclPS - orderby and orderDescending - to support single link navigations to attributes used in order. We have introduced a new type of nesting in the ViewModel. This Nesting is a SearchExpression-nesting with a name starting with OrderExpression. If the SearchLogic finds this, we now grab the first active criteria from there and extract the OrderBy/OrderDescending expression-part - this part is appended to the combined resulting expression from the original part of the searchlogic. The video discussing this can be found here: https://www.youtube.com/watch?v=55K0irODBRE
- Use the tagged value Eco.HiliteGridColumn to signal the user to sort order/searched column that...
- Find it by clicking on the orange search-expression and using the "Tagged value" button or the property inspector.
The MDriven Book - Next Chapter: Efficient ViewModel fetch