One thing about OO implementing an MVC architecture in a stateless environment has eluded me. How does one cleanly manage interactions between the client and the server once the client has received its rendered HTML?
Consider this; I have a requirement in a current project to show several tables of data.
Data in these tables need to be sortable. When you click on a column header the data in the table will refresh and be sorted according to the data in the column.
The table of data also needs to be filterable. A filter could be applied to the data to show only rows where the last name starts with “H” or perhaps where some action is required.
Lastly the data displayed in the table needs to be nicely printable as a PDF or to FlashPaper. The resulting printed version would reflect the current state of the table showing only the filtered and sorted data.
An important consideration is that the data displayed in these tables will change throughout the application. For instance, in one case you might be viewing a table of users. Each user’s name might be a link to another page. Or, you might see a table of categories with each category having a checkbox you could select. (In this latter case the table would be shown in a form and some form action would deal with the selected categories.)
The point is that I want a generic system to flexibly create these storable, filterable, paginated tables.
Traditionally, I would probably just write some code that output a query into an HTML table. The output code would be one-off and written to let me do exactly what I need. So, if I needed a user’s name to be a link I’d simply surround it with a link tag. The column headers would be links back to the same page and would cause the server to rerun the query, but sorting it according to the selected columns. To filter data I’d have a form or links that would ultimately reload the page again to filter the data.
One of the tenets of Object Oriented Programming is encapsulation. In a nutshell encapsulation means to hide your object’s implementation. Whatever uses your object shouldn’t care about how your object does its job, just that the job is done (and done correctly). So, with my tables of data, I want to encapsulate the process of creating the table and all the logic involved with sorting and filtering and printing it. I really only want to define what’s different between tables and have some other system automatically handle it.
What’s befuddling me is that the requirements genuinely requires interaction with the server from the client at least to print data.
In an effort to work through this problem let’s say that I temporarily forget that there is a requirement to view the table and pretend that I’m only working with data on the server. I could create a table component with an interface like this:
Table = init(data data, tableConfiguration Config) – This would be a constructor for the Table. When constructed the table would receive a set of data and configuration settings. For now, I’m going to ignore the format of the data. You can imagine it’s a query or XML if that makes you feel better, but I don’t think it’s necessary yet. The configuration settings would probably specify column names in the table and the column’s datatype.
sort(iSort Sorter) – This method on the Table component would receive an object that implemented an iSort interface (ok, in ColdFusion this would be convention and not a requirement). The iSort interface would probably define a method to compare items of data from the Table. The Table would know how to use the Sorter’s interface and would sort the data accordingly.
Here’s a pseudo code example to illustrate this:
<!--- create a table that holds data ---> <cfset Table = CreateObject("Component", "model.table.Table").init(data) /> <!--- create an alphabetical sorter to sort the data ---> <cfset Sorter = CreateObject("Component", "model.sort.alphaSort").init("lastName", "ascending") /> <!--- pass the sorter into the table ---> <cfset Table.setSort(Sorter) />
As of now the Table’s data would be sorted how I’d like it to be.
filter(iFilter Filter) – As with the sort method, this would receive a Filter object that implemented an interface that the Table knew how to use to filter the data. The iFilter interface would probably have a method that checked if an item of data matched the filter or not.
I start running into problems when I begin thinking about how to create visual representations of the data. In other words, how the heck to I translate the data in the component to HTML?
Well, you could, in theory, write a custom tag that knew the Table’s interface and could read data out of the Table and convert it into an HTML table. The problem is that this “breaks” encapsulation. If a component exposes its internal data to an external object such as a tag, then that data isn’t really encapsulated. You might as well store it in a structure. So, I don’t really like the idea of a tag.
I could also add a Render method to the Table component. Ideally this method would return the HTML to display the component. I’m not sure how I feel about this because it feels like the Table is beginning to take on too many responsibilities, but this is more of a gut feeling. It seems to beat passing the component into a tag.
The other nice thing about a render method on the table is that I could also add a print method that called the render method and then translated the resulting HTML into PDF or FlashPaper format.
So far, this seems like a reasonable solution. However, how do I sort and filter data once the Table is rendered to HTML and the HTML is displayed in the user’s browser?
The only answer I have to this question is that I cache the Table on the server and require the user to make a second trip (which I already don’t like) to the server which will then resort the cached Table. I don’t like this because it feels like I no longer have one component, but a set of related systems. I have the Table itself, but the Table has to know what events or pages to call on the server to get resorted. The Table has knowledge outside itself. Encapsulation is broken.
I suppose a print method could accept a URL and that the print method could get the Table’s rendered HTML and submit it to the provided URL which would be required to return the PDF or FlashPaper data.
What do you think? How would you tackle something like this?