Convert Grails 2 View to Use Knockout JS

Sep 20 2012

When the basic functionality of a web application has been developed, it’s time to add interactive features. Filtering a list of users is one task that is better done on the client side than on the server, to provide faster response. First, the Groovy variables need to be converted to JSON to be rendered in the view.

def newMessage = {
  def user = User.get(session.user.id)
  def account = CustomerAccount.get(session.account.id)
  [messageInstance: new Message(params), usersForAllDepartments: userService.getUsersForAllDepartments(user)]
}
def newMessage = {

  ...
  [messageInstance: new Message(params), usersForAllDepartments: userService.getUsersForAllDepartments(user).collect{[id: it.id, name: it.name]} as JSON]
}

Next, change the looping constructs and logic to use Knockout:

<div class="select-box"><label><span class="mark m-red">${user.name}</span></label></div>
<!-- ko foreach: recipients -->
<div class="select-box"><label><input type="checkbox" name="recipientUserList[]" data-bind="value: id, checked: selected" /></label></div>
<!-- /ko -->

Simply replace the tags with Knockout’s containerless control flow tags and bind to data values. Knockout is more terse, as the user for each iteration doesn’t need to be referenced. Render the JSON list of users on the page before the main part of the JavaScript code, which I like to keep in a separate deferred (CoffeeScript) file.

<script type="text/javascript">// <![CDATA[
usersForAllDepartments = ${usersForAllDepartments};
// ]]></script>

Finally, apply Knockout’s bindings to it with the user list loaded.

function Recipient(data) {
  this.name = ko.observable(data.name);
  this.selected = ko.observable(false);
  this.id = data.id;
}
function MessageViewModel() {
  var self = this;
  self.recipients = ko.observableArray([]);

  // Load initial state
  var mappedRecipients = $.map(usersForAllDepartments, function(item) { return new Recipient(item) });
  self.recipients(mappedRecipients);
}
ko.applyBindings(new MessageViewModel());

The rendered HTML looks exactly the same as before (except for data-bind).

<div class="select-block flexcroll">
<!-- ko foreach: recipients -->
<div class="select-box"><label><input type="checkbox" name="recipientUserList[]" value="5" data-bind="value: id, checked: selected" /><span class="mark m-red" data-bind="text: name">Bobby Tester</span></label></div>
<div class="select-box"><label><input type="checkbox" name="recipientUserList[]" value="3" data-bind="value: id, checked: selected" /><span class="mark m-red" data-bind="text: name">Manfred Moser</span></label></div>
<div class="select-box"><label><input type="checkbox" name="recipientUserList[]" value="2" data-bind="value: id, checked: selected" /><span class="mark m-red" data-bind="text: name">Sys Admin</span></label></div>
<!-- /ko -->
</div>

One response so far