Hey folks,
At Elemica we’ve run into a lot of situations in the past several months where Lift’s standard radio handling yields incredibly undesirable behavior. The wholesale replacement of elements on the page means that if we’ve applied any meaningful attributes to them we have to make sure those are mirrored in the Scala invocation of radio* (e.g. the id of the radio that a label tag may depend on) and the hidden fields that the default Lift handling injects for radios causes styles in CSS that depend on ordering (e.g. “input[type=radio] + label” or “the label immediately after a radio button”) to not behave as we’d expect them to.
The radioCssSel method introduced in that commit provides a solution to these problems.
By binding the radio button using CSS selectors, we leave unaltered attributes (e.g. id and class) left alone. We only set the name, the value, and the checked attributes on the input nodes. Radios are associated using CSS selectors instead of needing to reference the index of a ChoiceHolder to get at the exact radio button instance.
So, let’s take this example to illustrate what I’m talking about:
<label for=“name”>
<input type=“text” id=“name”>
<input type=“radio” id=“get-all-emails”>
<label for=“get-all-emails”>
<input type=“radio” id=“get-no-emails”>
<label for=“get-no-emails”>
This is a simple form set up with some best practices. Here, every field is labeled and (thanks to the for attribute in label) you cal click the label in the browser to actually select the associated input. They’re linked by the id of the input. This is a huge usability win because that makes the clickable area for selecting a radio button more than just the circle of the radio. It’s easier to hit.
So, with my new radioCssSel code you could write:
“#name” #> text(name, name = _) &
radioCssSel[Boolean](Full(true), emailSelection = _) {
“#get-all-emails” -> true,
“#get-no-emails” -> false
}
And it would continue to behave as the original author of the HTML intended. By contrast, if you were to attempt to do this using the current radioElem method you’d have to do something like:
val radios = radioElem[Boolean](
true :: false :: Nil,
Full(true)
)(emailSelection = _)
“#name” #> text(name, name = _) &
“#get-all-emails” #> radios(0) % “id” -> “get-all-emails” & //preserve ID
“#get-no-emails” #> radios(1) % “id” -> “get-no-emails”
This radioElem has a few disadvantages:
- We’ve got to duplicate the ID in scalaland to preserve it so the labels continue to function correctly. Not to mention any css classes or anything else that might also be applied.
- This will result in the insertion of hidden input fields between the radios and the labels which will mean that styles based on position won’t work as intended.
- The values are separated farther away from what their bound to. Using the radioCssSel method it’s clear that #get-all-emails is the true radio. Using radioElem it’s not quite as clear. Perhaps when you only have two values the difference is minimal, but if you scale up to more than 2 radios it can quickly become cumbersome to try and figure out which radio causes what value to be populated.
I’d like to propose we include radioCssSel in Lift 3, and perhaps we start thinking about replacing radioElem wholesale in a future release of Lift. Also, a hat tips go to Antonio Salazar Cardozo who I believe contributed the original ideas around how this could work and Piotr Dyraga for making some contributions to this code structure as well. (And if I forgot someone else on the Elemica team who helped then please accept my apologies.)
Would love thoughts and comments on this.
Cheers,