An extended CRUD interface for Lift/MongoDB object reference fields

November 03, 2012 – tagged Lift, Scala, Programming, Howto

In a previous blog post, I explained the straight-forward way to generate a CRUD interface in Lift for MongoDB's reference fields. The approach described there, using a simple multiselect list, is very easy to realize, but the situation is a bit different when the list of objects that can be referenced is becoming larger and larger, since Lift needs to

  • load all of those items from the database,
  • assign unique IDs to them and keep them in memory, and
  • transfer the complete information to the browser.

A more “scalable” way to realize the same interface is provided by select2's capability to call a query function with the current text from the widget in order to get the autocomplete result. We can create a function in Lift that takes JSON of the form

{"term": "abc", "page": 1}

and returns JSON of the form

{
  "results":[{
    "id":"501c4d110ecffa779751eabd",
    "text":"Aabcd"
  },{
    "id":"501c4d220ecffa779751eaf1",
    "text":"Babcef"
  }],
  "more":false
}

and bind it to the query parameter of select2, attached to an input element of type "hidden".

However, if there is just one such JValue => JValue lookup function for the whole page, then we can't filter for valid referenceable objects per field, as we could do for the multiselect widget by overriding the options function of ObjectIdRefListField. So what we need to do to overcome this is:

  • Define a function definingQuery per ObjectIdRefListField that determines what objects can actually be referenced (the substitute for the options function),
  • define one JValue => JValue function per ObjectIdRefListField that takes into account the value of definingQuery when doing the auto-complete lookup,
  • change toForm to output a <input type="hidden"> element instead of the multiselect list and a JavaScript function to call the JValue => JValue function from above,
  • tell select2 to use that JavaScript function for looking up the auto-complete items,
  • and finally, embed the initial value of the field to the data-initial attribute of the hidden input widget and tell select2 to use that data as the initial value.

My One2ManyCRUD trait from the previous blog post has now grown to the following:

The two abstract functions `searchMeta` and `definingQuery` that are used in `One2ManyCRUD.matchingEntries` must be overridden in the model class introducing the `ObjectIdRefListField`:

The Searchable trait must be mixed in from the MetaMongoRecord of the referenced class, because that class itself knows best how to lookup its objects by a given string:

The HTML code that will now be generated by `One2ManyCRUD.toForm` looks as follows:

The last step is to tell select2 do use the selectTerm_*** functions as a query function and the data-initial field as the initial value:

This is a scalable solution that

  • uses constant-size HTML,
  • doesn't fetch all referenceable objects from the database,
  • and allows to add objects to the database and reference them afterwards without reloading the edit interface.

However, some things in here don't exactly feel right, like

  • splitting the JSON for the MongoDB lookup into parts in the Searchable trait and parts in the One2ManyCRUD trait,
  • having one jsonCall per <input> field and obtaining the function name by string concatenation,
  • introducing searchMeta as basically an alias for refMeta, just with a different return type,

so I'm happy for any ideas how to improve on this.