Pages

Tuesday, September 20, 2011

Table-less Forms: A Partial Solition (Source Code)

In my last two posts, I discussed what I want to see in a table-free form and the problems I had with creating one. To recap, here's what I wanted:
  • Labels lined up in the same row as form fields
  • Label column that stretches/shrinks to the size of the longest label
  • As little extra HTML as possible
My solution was to enclose <label>s in a <div> that stretches/shrinks to the width of its contents, and to use absolute positioning to push the <input>s nested in those <label>s outside the <div>. The limitations of that solution are:
  • The <label>s' height won't stretch to fit form controls taller than a line of text, such as <textarea>.
  • Since <input>s are pushed to the right, you can't put checkboxes and radio buttons before their label text unless they're outside the wrapper <div>.
  • Long labels stretch the wrapper <div>.
  • Multiple wrapper <div>s in a form won't line up with each other.
So with that in mind, here's my source code. First the CSS:
form { /* Add border to form as visualization aid */
 border: 1px solid darkblue;
}
form * {
 clear: left; /* don't let floated .formlineup break layout. */
}
.formlineup {
 display: inline-block; /* shrink to fit content. */
 float: left; /* Needed for backward-compatibility with IE6 and FF2. */
 background-color: lightblue; /* Visualization aid */
}
.formlineup label {
 display: block;
 position: relative; /* allow absolute positioning of children */
 padding-top: 2px;
 margin-top: 2px;
 padding-right: .5em;
 background: #CEF; /* visualization aid */
}
.formlineup label input {
 position: absolute;
 top: 0px;
 left: 100%; /* Push all the way outside the <label> */
 height: 1em;
}
/* <br> tags are used in case CSS breaks.
They create extra space when CSS is working, so hide them. */
.formlineup br {
 display: none;
}

Next, the HTML. Note that nothing outside of div.formlineup is really important for this demo; I put the extra fields there to show some of the things that need to be outside of the wrapper div in order to work. See the screenshot in my previous post to see what happens when you put them inside.

<form>
<div class="formlineup">
 <label for="name">
  Name:
  <input type="text" name="name" id="name">
 </label><br>
 <label for="address">
  Address:
  <input type="text" name="address" id="address">
 </label><br>
 <label for="stuff" style="clear: left;">
  Stuff:
  <input type="text" name="stuff" id="stuff">
 </label><br>
 <label for="password">
  Password:
  <input type="password" name="password" id="password">
 </label>
</div>
<fieldset><legend>Radio Buttons</legend>
 <label for="thing1">
  <input type="radio" name="thing" value="1" id="thing1">
  Thing 1
 </label><br>
 <label for="thing2">
  <input type="radio" name="thing" value="2" id="thing2">
  Thing 2
 </label><br>
 <label for="voom">
  <input type="radio" name="thing" value="voom" id="voom">
  <em>VOOM!</em>
 </label><br>
 <label for="long">
  <input type="radio" name="thing" value="long" id="long">
  This label is longer than the others.
 </label><br>
</fieldset>
<label for="spam">
 <input type="checkbox" name="spam" id="spam">
 Send Me Spam!
</label><br>
<input type="submit"><input type="reset">
</form>

Addendum: Sorry about the ugliness of the source code; Blogger doesn't feature syntax highlighting out of the box, and there's some investment of effort required to add it. I'm still debating whether I want to add it or just stick to using my Wordpress blog to post source code from now on.

Wednesday, September 7, 2011

Table-less Forms: They Are Really That Hard.

A couple weeks ago, I posted a list of criteria for what I'd like to see in a CSS-based form that emulated the nifty features that tables provided. To recap, I wanted:
  • Labels lined up in the same row as form fields
  • Label column that stretches/shrinks to the size of the longest label
  • As little extra HTML as possible
The good news: I quickly came up with a solution that met all my criteria. The bad news: It only works with simple forms. ("I quickly came up with a solution" should have been a tip-off!) First, I'll explain what I did, and then I'll explain why it can't handle much complexity.
My solution was to nest the inputs I wanted to align inside a div, style the div so it would shrink to the width of its contents, and then use absolute positioning on the inputs (nested inside relative-positioned <label>s) to push them to the left by 100% of the width of the div.
Screen shot of form lineup test, working so far. Four text fields are aligned as expected. Radio and checkbox inputs outside the wrapper <div> are unaffected.
If it sounds too easy, that's because it is. Here's what doesn't work:
  1. It only works with inputs that are the same height as the labels, which means it won't work with <textarea>s, multiple-row <select>s, and probably others I'm forgetting.
  2. Checkboxes and radio buttons inside the wrapper div can't be put before their labels because the positioning automatically puts the inputs on the right.
  3. Longer labels, such as you might need to ue for checkboxes and radio buttons, will stretch the wrapper <div>'s width.
  4. Those last two aren't problematic if you can group all your text inputs together and save everything else for the end. If you want to put anything between two groups of text inputs, though, you'll need two separate wrapper <div>s, which won't line up with each other.
Screen shot of broken form lineup test. A <textarea> does is overlapped by the text field after it, checkboxes and radio buttons are in the wrong places, and the label column is stretched to fit the fieldset.


I will post the code as soon as I can get it cleaned up and looking nice. (My test code is embarrassingly messy.) I also plan to revisit this project and see what fixes I can make. The checkbox problem at number 2 shouldn't be too hard to solve. Then again, that's what I said about CSS forms in general.