One of the most common requirements when developing web applications is some kind of mechanism for getting and displaying a collection of object instances. Listing pages, users, articles and products are all very common requirements. If you were to create a method to handle the returning of lists, what would its interface have to be? The list of properties we use are as follows:
- Base Filter - The default filter that is always applied to the list. If it is a list of Admin users, it might be where "UserAdmin = 1".
- Custom Filter - An optional custom filter which may be passed from the view. Example would be a list of products within a category as the base filter, but priced between $10 and $25 would be an additional custom filter set by a user for a particular request. Should support multiple terms (e.g. custom filtering by price, manufacturer and material).
- Default Order - The default order in which the collection should be returned.
- Custom Order - The optional custom order to overload the default order if it is set - usually by a user selecting a specific property to order by for a given page request.
- Property Name List - A comma delimited list of the property names (including aggregate has-many associated properties, has-one associated properties and calculated properties) that the query should return.
- Page Number - Which page of records to return (defaults to 1).
- Records Per Page - How many records to include in each page returned (defaults to 0 which disables paging).
Side note: I haven't explicitly mentioned anything like excluding soft deleted records or handling tables where there are different objects in the same table - those should be handled by the object metadata so if there is a base filter like Customer=1 to return Customers from the Company table or a Deleted=0 to exclude soft deleted record, that should be taken care of automatically at a lower level rather than being repeated in every list related to the object.
There are three levels that the properties need to be settable at - model, controller (static) and controller (dynamic). The definition of a given list method in the model (say getAdminUserList()) should be able to optionally set all of the above properties, with the following defaults for any properties not set:
- Base Filter - Defaults to nothing - no where clause.
- Custom Filter - Defaults to nothing
- Default Order - Defaults to default order by for the object.
- Custom Order - Defaults to nothing
- Property Name List - Defaults to all of the defined properties for the object.
- Page - Defaults to 1
- Records Per Page - Defaults to 0 (disabling paging).
It is useful if the definition of a controller method can overload the default order by, property name list and records per page properties. In that way if you have three different controller actions that all need to display a list of admin users but with different orders, property name lists, and/or default records per page, you only need four methods (three controller, one model) rather than six (three controller and three model). I draw the line at being able to change the base filter because ontologically I think a given list is fundamentally about the records that are included/excluded, but not about the properties returned, the default order or the number of records returned per page.
In addition, the controller needs to be able to optionally dynamically set the custom filter, the custom order, the property name list, the page and the number of records per page in response to any inputs from users.
The question then is what convention to follow for user input relating to page number, property name list, custom order, records per page and any custom filters to allow generalized List controller code to find those automatically.
There needs to be a mechanism whereby a list controller can obtain state for a given list - whether from URL/form or session/cookie scope (or some combination thereof). The question is how best to handle this. Let's start with the simplest case where no settings are stored in session or cookie store as user preferences (I have a sense of how I'd implement that, but based on YAGNI I'm not going to worry about it just yet as it is a fairly uncommon request from our client base).
The first question is whether properties relating to a list need to use some kind of convention to associate their properties to the list - is there ever any chance that the page number for one list could affect another list being displayed on the page at the same time? The way we architect applications, there is a primary content area which consumes all form and URL scoped variables, with additional dynamic areas either having service method calls at the top of the view (if reuse of the view isn't a concern) or calling their own independent controllers which have access to but don't typically use URL and form variables. This means that if you want to be able to page through a list on a secondary content area you'd need to use AJAX, but while this would appear to be a limitation, it really hasn't been a problem for any of the apps we've developed.
The second question is how (or whether) to handle detail views in the primary content areas with multiple composed lists. Imagine a User detail screen in the main content area with a list of addresses, a list of orders and a list of trouble tickets. We treat that as a simple detail template with composed lists. How do you handle the potential need for paging/ordering/filtering within those composed lists? There are two approaches. One is to try to keep track of the independent state of each list either through session or clever use of form/URL scope. I would probably create one variable for each list containing all of the filter/page/records per page/order info and write a smart URL builder to handle creating links for each list that keeps the state for all of the other lists. Another approach would be to store the state for each list (perhaps based on a list ID) in session state until it is explicitly changed by a List7PageNumber=3 kind of URL or form scope variable. However, we've decided to take another approach.
So, we'll just use a convention of URL/form variables for PageNumber, RecordsPerPage and OrderBy properties for lists. For filters, you could potentially need to filter by n-properties each with a different filter type (greater than, like, less than, contains, etc.). We'll work up a simple XML syntax for describing filters in a bit, but for now I want to implement the slightly upgraded core list functionality with order, list and paging capabilities by upgrading the list base controller method and creating updated list helper methods in a list.cfc to handle creating the links and the like for records per page, ordering and paging through lists.