Grails Design Consideration #2 – Throw In a View Model Once In a While – Ted Vinke’s Blog
This is my take on how we could design our particular user interface in such a way that they’re re-usable, testable and the overall software is more maintainable. Yip, using a bit of View Models from the MVVM pattern.
Background
Recently we started working with two teams on a new Grails application.
As part of that I reviewed some code of the teams’ earlier applications to come to some general coding standards & conventions both teams would follow for the new application. In these code review sessions I’m also giving advice on some architectural choices and design style I would prefer.
Use Case: Tasks
The new application’s homepage displays the logged in user, a overview with tasks grouped by month and some unrelated sliders.
E.g. something like this:
We did already create a good main-layout (grails-app/views/layouts/main.gsp) with header, content block and footer. In the first few days of the sprint, when the initial design and assets were also put in the Grails application, a story was underway to implement this overview by, let’s say, a team member called John.
For this, John created a controller (HomeController) and GSP (home/index.gsp).The header and footer were already extracted into the main layout, so an initial empty GSP…
<%@ page contentType="text/html;charset=UTF-8"%> <html> <head> <meta name="layout" content="main" /> <title>Tasks</title> </head> <body> </body> </html>
…quickly started filling up with quite complicated HTML. Despite what the abstract screenshot above tells you, the actual tasks overview contained quite some SVG imagery – taking a lot of space.
After the tasks overview ends there are a few sliders present.
If you ever have gotten static HTML from a design agency you’ve experienced this already, but at some point you have to make stuff work with actual content instead of the regular lorem ipsum.
The tasks overview shows tasks grouped per month (January, February, Match etc) of the current year. The view however should not always start at January, but at the current month – so the most urgent tasks are on top. That’s at least the requirement at the moment.
A second move of John was to move the hard-coded values from the HTML to the application itself and provide it the Grails way to the GSP; through the controller in the model.
The HomeController
looked somewhat like this:
class HomeController { def securityService def taskService def index() { int showPercentage = 3 Map<String, List> tasks = retrieveTasks() Map<String, Integer> notifications = retrieveNotifications() def nowDate = new Date() def currentMonth = nowDate[Calendar.MONTH] + 1 return [ username: securityService.user.name, tasks : tasks, showPercentage: showPercentage, months : getMonths(), startMonth : currentMonth, notifications : notifications ] } private Map<String, List> retrieveTasks() { taskService.retrieveTasks() } private Map<String, Integer> retrieveNotifications() { taskService.retrieveNotifications() } List<String> getMonths() { return [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ] } }
The model had a username (to display “Hi John”) and a variety of variables (tasks
, showPercentage
, months
, startMonth
, notifications
) needed for the tasks overview and sliders at the bottom.
Consequently index.gsp was updated to take the values from the model.
<body> <div class="container"> <div class="pat-well"> <div class="row month-${startMonth}" id="task-browser"> <div class="six columns"> <g:if test="${notifications}"> ...
So, that’s the initial version and it works.
Model-view-viewmodel
When looking at the code of the controller and what’s ultimately in the model, one can not easily tell what is related and what’s not.
return [ username: securityService.user.name, tasks : tasks, showPercentage: showPercentage, months : getMonths(), startMonth : currentMonth, notifications : notifications ]
Also variables are put in the model by seemingly random order – may be in the order they were created, needed in the GSP or something else. These Bulk Models are not uncommon – and this is just “only” 6 things returned – for various parts of the UI.
Maybe we can clean this up a bit.
Part 1 – View Model
Let’s introduce a View Model, a plain-old Groovy object (POGO) just for the purpose of holding related data for a view “component”. Hey, this is the first time I hear you mentioning a component. Yes, it’s one of those overloaded terms which can mean anything. That’s fortunate, because we can create also POGOs for anything ;-)
Create a new class at the bottom of HomeController.groovy and name it according to whatever your UI widget or component represents. I kept saying “tasks overview” up until now and could have called the new class: TasksOverview
.
In this particular case we’re getting the layout from an external webdesign agency and they usually also think about naming objects, for use as CSS selectors or JavaScript objects. A look in the source revealed:
<div class="row.." id="task-browser">
Task browser!
Especially when in a project where a lot of UI elements are used, it’s important to name things early on between design and development teams. You just can’t keep saying that hourglass thingy in the upper-right corner.
The TaskBrowser
class can also be a seperate TaskBrowser.groovy
in the src
folder, but right now it’s only used on the homepage so just putting it below the HomeController
class in HomeController.groovy
will do for now.
... } /** * Task browser. */ class TaskBrowser { }
Move every associated property which holds data from the controller’s index() action to this new class, e.g. the tasks
and notifications
collections.
class TaskBrowser { Map<String, List> tasks = [:] Map<String, List> notifications = [:] }
Create an instance of TaskBrowser, either in the action itself or through a helper method e.g.
private TaskBrowser createTaskBrowser() { return new TaskBrowser( tasks: taskService.retrieveTasks(), notifications: taskService.retrieveNotifications() ) }
We’re still left with some date/time handling of building a list of months and determining the starting month for the task browser.
Even though this class seems just a bag of related data, don’t forget proper OO principles and move that responsibility also to the component class if possible e.g.
class TaskBrowser { Map<String, List> tasks = [:] Map<String, List> notifications = [:] int getStartMonth() { def nowDate = new Date() nowDate[Calendar.MONTH] + 1 } List<String> getMonths() { return [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ] } }
The controller action can now return a TaskBrowser
in the model which hereby is not only trimmed down to “just 3 things” but it’s more clear what’s related.
def index() { int showPercentage = 3 return [ username: securityService.user.name, showPercentage: showPercentage, taskBrowser: createTaskBrowser() ] } private TaskBrowser createTaskBrowser() { ... }
Part 2 – The View
We’ll, that’s great, but we’re not there yet: things from the TaskBrowser
are still used in various parts of the home/index.gsp – basically changes need to be made still all over the place.
Remember our index.gsp being a mix with general HTML flowing into a specific component, such as the task browser:
<body> <div class="container"> <div class="content"> <div class="row month-${startMonth}" id="task-browser"> <div class="six columns"> <g:if test="${notifications}"> ...
In our current setup it’s essential that
- whoever is working on the task browser making it work with real data from the system
- whoever is implementing UI changes in the task browser as tweaks are coming in from the web designer
- whoever is working on other features of the homepage
don’t clash too much.
That means they shouldn’t work much on the same lines of code in the application if we can prevent it. And we can.
Template
The easiest thing one can think of is extracting a large chunk of HTML from a GSP into a separate snippet or template.
Extract the HTML from the index.gsp into a smaller GSP, called e.g. _taskBrowser.gsp
, which ultimately can be included like in index.gsp with <g:render>
<body> <div class="container"> <div class="content"> <g:render template="taskBrowser" />
This renders nice and cleanly isolated away HTML in the template such as
<div class="row month-${startMonth}" id="task-browser"> <div class="six columns"> <g:if test="${notifications}">
Seems ok, until you realize you still have to pass along the model to the template, before the template can access all needed variables. Until then, not very useful.
<body> <div class="container"> <div class="content"> <g:render template="taskBrowser" model="[ tasks : taskBrowser.tasks, months : taskBrowser.months, startMonth : taskBrowser.startMonth, notifications : taskBrowser.notifications ]" />
Bit verbose. A shorter version would be using the bean
attribute.
<g:render template="taskBrowser" bean="${taskBrowser}" />
In this case, we can not access the properties from the taskBrowser
until we change the template to get everything from the implicit it
.
<div class="row month-${it.startMonth}" id="task-browser"> <div class="six columns"> <g:if test="${it.notifications}">
It’s a trade-off, but we made the life of the index.gsp’s author a bit easier by just needing 2 things for it to work: a template name and the data.
The name of the template to include can still be considered an implementation detail. Renaming or replacing it entirely involves updating all GSPs which might render this specific template by name, and thus – if you have more templates like this – might become a maintenance burden.
Let’s see if we can do better with a …
Tag Library
The View layer of a Grails application is not just the Groovy Server Pages (GSPs) in the grails-app/views-directory, but also includes Grails’ tag library mechanism. A tag library is a kind of “view helper” in the Model View Controller Pattern (MVC).
Create a tag library, called e.g. LayoutTagLib
in grails-app/taglib, which helps putting “layout” things on a page. Pretty generic name, but it can always be split up in more specific tab libraries if it grows too large.
/** * Layout tags. */ class LayoutTagLib { static defaultEncodeAs = [taglib:'html'] }
Implement a tag called taskBrowser
which accepts a single attribute taskBrowser
– which is required. No body needed for this one.
/** * Layout tags. */ class LayoutTagLib { static defaultEncodeAs = [taglib:'html'] /** * Renders the task browser. * * @attr taskBrowser REQUIRED a task browser instance */ def taskBrowser = { attrs -> if (!attrs.taskBrowser) { throwTagError("Tag [taskBrowser] is missing required attribute [taskBrowser]") } } }
The
throwTagError
will throw aGrailsTagException
stating what’s missing if a user of the tag forgets to pass along the required attributes. The “@attr REQUIRED” part is for IDE support and/or documentation.
Render the earlier template giving it the correct model. Since _taskBrowser.gsp
now becomes the responsibility of this tag lib move it to a new, more neutral location such as e.g. grails-app/views/layouts/components.
/** * Layout tags. */ class LayoutTagLib { static defaultEncodeAs = [taglib:'none'] /** * Renders the task browser. * * @attr taskBrowser REQUIRED a task browser instance */ def taskBrowser = { attrs -> if (!attrs.taskBrowser) { throwTagError("Tag [taskBrowser] is missing " + "required attribute [taskBrowser]") } TaskBrowser browser = attrs.taskBrowser out << render(template: '/layouts/components/taskBrowser', model: [tasks : browser.tasks, months : browser.months, startMonth : browser.startMonth, notifications : browser.notifications ]) } }
Update
defaultEncodeAs = [taglib:'html']
to[taglib:'none']
– to prevent our rendered GSP snippet being HTML encoded.
Jip, we’re still passing each individual parts to the model here, but of course you could also the bean
variant if you’d like.
The index page now becomes:
<body> <div class="container"> <div class="content"> <g:taskBrowser taskBrowser="${taskBrowser}"/>
It helps to think of the TaskBrowser
as the input for the taskBrowser
tag. Since we’re in tag library world now, we might do some additional logic or processing based on this input (e.g. filter the tasks to be shown by a certain task type first) and pass processed data further to the template in the model.
If such additional input, e.g. for a filter, is optional – and depended on the place in the application where the tag is used, you could accept it as an optional attribute instead of placing it in the TaskBrowser
object.
E.g. an additional filter
attribute could work like
/** * Renders the task browser. * * @attr taskBrowser REQUIRED a task browser instance * @attr filter Optionally a {@link Task.Type} to show only those tasks */ def taskBrowser = { attrs -> if (!attrs.taskBrowser) { throwTagError("Tag [taskBrowser] is missing " + "required attribute [taskBrowser]") } TaskBrowser browser = attrs.taskBrowser // filter tasks by type def tasks = browser.tasks if (attrs.filter && attrs.filter instanceof Task.Type) { tasks = browser.tasks.findAll { task -> task.type == attrs.filter } } out << render(template: '/layouts/components/taskBrowser', model: [tasks : tasks, months : browser.months, startMonth : browser.startMonth, notifications : browser.notifications ]) }
A page can ignore the attribute:
<g:taskBrowser taskBrowser="${taskBrowser}" />
Another page can use the filter:
<g:taskBrowser taskBrowser="${taskBrowser}" filter="${TaskType.PERSONAL}" />
Again, this is just one way. Another valid approach could certainly be to place this kind of stuff (e.g. filter options) still be in the original view model (TaskBrowser
class) if different controllers create different instances for different purposes. What works best is dependent on the use case — as always.
Voila!
Should I create a specific View Model for everything?
Of course not. Take for instance the slider.
If it only takes a few attributes such as value
below…
<g:slider value="${showPercentage}" />
it would seem overkill to wrap/get showPercentage
in/from a specialized object. Just pass such values directly to the GSP.
class HomeController { def index() { ... return [..., showPercentage: 3] }
Remember that we started out with a controller which returned a big cluttered model with all kinds of stuff in there?
return [ username: securityService.user.name, tasks : tasks, showPercentage: showPercentage, months : getMonths(), startMonth : currentMonth, notifications : notifications ]
Afterwards we grouped related stuff into View Models to see what belongs to each other. If you find yourself still ending up with a dozen “just simple values” in the model, this might be a signal that it’s still possible to find some relations in there, candidates to be grouped together after all.
Aren’t my domain classes not already a View Model?
Sure. For CRUD screens the information you need to display is basically exactly what you have in your domain class. Most of the time. Ok, not entirely true for more complex administrative front- or backends, but that’s what it seems if you look at some scaffolded controllers which work perfect for simple scenario’s.
I wrote an earlier post about for some domain class-centric things to be aware of, even in simple cases ;-)
Should my tag always render some kind of template?
Surely not.
As you can see from
def taskBrowser = { attrs -> ... TaskBrowser browser = attrs.taskBrowser // filter tasks by type def tasks = browser.tasks if (attrs.filter && attrs.filter instanceof Task.Type) { tasks = browser.tasks.findAll { task -> task.type == attrs.filter } } out << render(template: '/layouts/components/taskBrowser', model: [...])
the filtering logic is done in the tag itself, but displaying 1000+ lines of HTML is delegated to the _taskBrowser.gsp. That’s the natural fit for large chunks of HTML, a GSP.
It’s OK to do small HTML stuff in the tag itself e.g.
def importantMonth = { attrs -> out << "<strong>${attrs.month}</strong>" }
HTML vs Code-Centric
There’s definitely a gray area here with tag libraries.
Next example tag is more code-centric with HTML in between.
def showMonths = { attrs -> out << "<ol>" months.each { month -> out << "<li>${month}</li>" } out << "</ol>" }
As you can see, it becomes harder to see the what’s going on since code is riddled with control flow and HTML output. If you would take a more HTML-centric approach you would end up with a GSP snippet like this:
<ol> <g:each var="month" in="${months}"> <li>${month}</li> </g:each> </ol>
In our case, where we need 1000+ lines of HTML of some complex Task Browser UI component, you’d be better off by putting that HTML in a template ;-)
In conclusion
A successful separation of concerns using a View Model and Tag Library allows for Component Thinking, which can lead to more maintainable code because of the parts which work together and should change together are in clearly, isolated and distinguishable locations in the code base.
Hopefully at least the thought process can help you a bit if you find yourself in a similar situation as we were in.
Consider throwing in a View Model once in a while ;-)
What about Testabillity, you say? In a future post I will describe how to test our new tag library.
Further reading
- Tag Libraries – Grails Documentation
- Grails domain classes and special presentation requirements –
Ted Vinke’s Blog - Model–view–viewmodel – Wikipedia
Reference: | Grails Design Consideration #2 – Throw In a View Model Once In a While – Ted Vinke’s Blog from our JCG partner Ted Vinke at the Ted Vinke’s Blog blog. |