Here's the code:
class AjaxForm { var state = AjaxForm.state var city = "" private def cityChoice(state: String): Elem = { val cities = AjaxForm.citiesFor(state) val first = cities.head // make the select "untrusted" because we might put new values // in the select untrustedSelect(cities.map(s => (s,s)), Full(first), city = _) } private def replace(state: String): JsCmd = { val cities = AjaxForm.citiesFor(state) val first = cities.head ReplaceOptions("city_select", cities.map(s => (s,s)), Full(first)) } // bind the view to the dynamic HTML def show(xhtml: Group): NodeSeq = { val (name, js) = ajaxCall(JE.JsRaw("this.value"), s => After(200, replace(s))) bind("select", xhtml, "state" -> select(AjaxForm.states.map(s => (s,s)), Full(state), state = _, "onchange" -> js.toJsCmd) % (new PrefixedAttribute("lift", "gc", name, Null)), "city" -> cityChoice(state) % ("id" -> "city_select"), "submit" -> submit(?("Save"), () => {S.notice("City: "+city+" State: "+state); redirectTo("/")})) } }