Wednesday, November 24, 2010

Tenneseans, I hope there's no disaster, but if there is, you're welcome.

 In 2005, there was a hurricane which forever changed New Orleans. But not only that, it woke up many people who were in the business of ensuring that poor people affected by those disasters had access to funds and services to which they were due by their government, their employers, et cetera. My wife was one of those people. As far as I can cobble together, disaster legal services was a much more organized and effective years before that, and it went in to a bit of a decline. Until we met Katrina.

So my wife, a former disaster legal aid attorney, asked if there was a way I could help coordinate communication between different groups. "Something like a listserv." I still find it adorable that she calls it a listserv. So I set up a mailing list so people around the country could share information. Nothing much, really, but it helped get people together.

I have come to find out that the Memphis Bar association has a nice condensed disaster relief legal assistance reference guide for volunteer attorneys. Stuff like this:
Q. 7 I received my check for rental assistance, but there are no places to rent. 

If you are eligible for housing assistance from FEMA but are unable to find a rental house or apartment within a reasonable commuting distance of your damaged home, please contact FEMA at 1-800-621-FEMA (3362) or visit a nearby Disasater Recovery Center. FEMA will evaluate your situation, and, if appropriate, may authorize a travel trailer or mobile home.
Going back to the title page it says:
With special thanks to the following contributors:

...
Robert Konigsberg
I didn't do much, but it's nice that it helped.

Thursday, November 11, 2010

Replacing my Mac Keyboard with a Logitech Internet Navigator

(Second in my Luddite Internet Technology Series)

The Caps Lock key on my Mac keyboard was not responding so I pulled out the first keyboard I could find lying around: the Logitech Internet Navigator keyboard. You know the type: the one with buttons labeled "Search", "Shopping", "Favorites". It probably came with a machine I bought long back when. But you know what? I love it.

It has some nice features I came to expect from my Mac keyboard: volume controls, and media play/rewind/pause buttons. All those work as you would expect. There's also a little scroll wheel on the keyboard which I don't really need since I have a scroll wheel on my mouse. Even the "Sleep" button brings up the sleep dialog box. The only thing missing is a replacement for the eject button. (Update: downloading the Logitech Control Center for OSX did the trick. Now the only thing missing is a USB port.)

The only change I had to make for it to work well with OSX was to swap the behavior of the Command and Option keys (Pressing Command was sending Option and vice versa.) So thankfully the OSX keyboard settings allow me to reprogram the function of those keys, so I implemented a modifier swap. (Update: Once I installed the Logitech Control Center, the keys were swapped once more, Restoring original behavior of keyboard modifiers did the trick, effectively making this paragraph "Remember to install the Logitech Control Center and do nothing else."

As for general keyboard behavior, the keys have a nice tactile feel, you don't feel like you're constantly hitting the bottom of the keyboard tray as you do when using the Mac keyboard. It comes with a keypad on the right side, which I've come to expect with all my keyboards. The keys are slightly more smooshed together. But hey, nothing is going to do it for me quite like my IBM Model M. I just wish in this case that for "IBM Model M" the M stood for "Macintosh."

In retrospect, the keyboard and mouse that came with my Mac Pro were probably the first two such peripherals that I have ever thrown out due to mechanical defect. Oh, and their earbuds. And on the other side of that is Logitech: with the exception of one piece of hardware, I've never been disappointed with their products, and as of today I'm no longer disappointed with that one exception.

It's nice to be able to bring a 2002 peripheral with so many silly buttons to just work naturally with a modern OS. Back in 2002, all those buttons were pie in the sky. Today they're launching my apps.

Sunday, November 07, 2010

Making a Table of Contents for Sites with Google Apps Script

At work I was building a document using Google Sites. While building them, the section headings were given their own numbers, like so:

Introduction
Part 1: Apples
  Introduction
  1.1: Fuji
  1.2: Macintosh
  1.3: Cameo
  Summary
Part 2: Pears
  Introduction
  2.1: Bosc
  2.2:D'Anjou
  2.3:Warden
  Summary

Yes, of course there's already a gadget you can use to create a table of contents for your page (Menu > Insert > Table of Contents). It's a rather nice gadget, but because I've intentionally numbered some sections and not others, there's a mental mismatch.

As you see, the Table of Contents generator gave section numbers to everything, so depending where you look, 2.2 is either Fuji apples or Bosc pears.

I didn't want to remove the section headings, and there was no real way to remove them via the Table of Contents gadget.

If you're not familiar with Google Apps Script, it's as you think: a scripting engine for Google properties. It's particularly powerful for scripting spreadsheets, and. as of October 22, you can process Google Sites with Google Apps Script.

Before going any farther, let me show you what I generated with Google Apps Script:

I'm no HTML Picasso, but it's not bad. Not bad at all. The links work, thanks to Google Sites automatically injecting <a name> tags with every <h[1-6]> tag.

The code

Let me share the code with you. First, here's how it's invoked:

function run({
  createToc(
    'https://sites.google.com/site/sitename/source-page',
    'https://sites.google.com/site/sitename/destination-page');
}



Yes, so you can specify a page from which to read and generate a second page with the table-of-contents included. (Side note: This actually gives me lots of ideas about workflow -- envision if the source URL was a private site where drafts were written, you could use Google Apps Script as a way to push from a staging area to production.)

OK, the code. It's mostly processing XML and generating HTML.

// I do not warrant this code in any way.
// If you break something important, don't come crying to me.
// Actually, check your Sites revision history first.
// Then go cry.

// Load content from fromUrl. Find the table of contents, and stick it in the document
// Save at toUrl.
function createToc(fromUrltoUrl{
  var page SitesApp.getPageByUrl(fromUrl);
  var content page.getHtmlContent();
  var doc Xml.parse(contenttrue);
  var root doc.getElement();
  var toc=[];
  parse(tocroot);

  var prefixDiv "<div id='kberg-toc'>";
  var postfixHtml "</div><div id='kberg-toc-coda'/>";
  var codaDiv "<div id='kberg-toc-coda'/>";

  var html prefixDiv tocToHtml(tocpostfixHtml;

  var index content.indexOf(prefixDiv);

  var newContent;
  if (index == -1{
    var "<div dir='ltr'>";
    index content.indexOf("<div dir='ltr'>"x.length;
    Logger.log("3: " index);
    var outdex index;
    newContent content.substr(0indexhtml content.substr(outdex);
  else {
    var outdex content.indexOf(codaDiv);
    newContent content.substr(0indexhtml content.substr(outdex codaDiv.length);
  }
  var newpage SitesApp.getPageByUrl(toUrl);
  newpage.setHtmlContent(newContent);
}

// Recurse through the XML, finding <h[1-5]> tags.
function parse(tocelement{
  var name element.getName().getLocalName().toUpperCase();
  if (name == "H1" || name == "H2" || name == "H3" || name == "H4" || name == "H5"{
    var level parseInt(name.substring(1));
    var anchorChildren element.getElements("a");
    var anchorText (anchorChildren.length 0)
        anchorChildren[0].getAttribute("name").getValue("";
    
    var item {
      level level,
      anchor anchorText,
      description element.getText(};
    toc.push(item);
  else {
    for each (var child in element.getElements(){
      parse(tocchild);
    }
  }
}
     
// Convert the table of contents entries into HTML.
function tocToHtml(toc{
  var html =
      "<div style='width: 250px; background-color: #e8e8e8;'>\n"
      "<br/><b>Contents</b><br/><br/>\n"
  for each (var entry in toc{
    var html2 indent(entry["level"]"<a href='#" entry["anchor""'>" +
        entry["description""</a><br/>\n";
    html html html2;
  }
  html html "<br/></div>";
  return html;
}

// Given an indentation level, return (2 * (level - 1)) non-breaking spaces.
function indent(level{
  var "";
  for (var 0level 1i++{
    "&nbsp;&nbsp;";
  }
  return x;
}


Final Thoughts

The remaining issue is triggering this script. Google Apps Script does have an event notification mechanism (e.g. run this script when the spreadsheet opens) but right now, Google Sites only has a time-based trigger (e.g. run this script every n minutes/hours.) To be honest, if an onSave trigger was written, would createToc(url, url), which would save the TOC to itself, trigger another onSave event? No big deal, if it results in a no-change, I could tweak the code accordingly.

But the lack of an onSave trigger makes this just slightly usable for me. Having this run every two minutes is too frequent for when the site isn't updated. I don't know the plans for the Google Apps Script team and/or the Google Sites team, but this is a good use case for onSave.

I wonder how hard it would be to hide the default TOC renderers' section numbers with clever CSS, or just write my own Google Gadget...

Tuesday, November 02, 2010

My new iPod Nano 6g

I wear out equipment pretty quickly. In fact I'm surprised my Nexus One has lasted for almost a full year without a major scratch. But iPods, well, those I wear out pretty quickly. And in the last two years I've really started to listen to podcasts and audio books, and so my iPods have been well loved.

My last one, the iPod Nano 4g had one newer feature that I really relied upon: sleep mode. I'd put something in the dock, and set it to sleep in 30 minutes. By then I'd be fast asleep. It was a good, reliable pattern.

Then my Nano broke. I pulled out a 2G Nano which worked, well, it worked OK but didn't have the sleep function. I'm still not ready to go all-out with my Nexus One. The battery life isn't quite ready for it, and, let's face it, I break stuff like that, and I'd rather keep it around for phone calls.

So I ordered the iPod Nano 6G.

In short, I just love it. Engadget has a review that covers the iPod's features; feel free to read that for something thorough.

The good:
  • The new UI is snappy and easy to use. The replacement of a the scroll wheel with a touch screen makes for easy interaction. I didn't have to learn how things worked, I figured it out naturally. It's meant to look like iOS, but isn't iOS. For instance, if my research is right, you can't write apps for it.  Given that there are five, pages for apps in the home screen and only fifteen apps, there seems to be natural room for expansion in future firmware updates.
  • Radio! When I showed this to my wife, she brought out her iPod envy. It's got a sufficiently workable radio receiver.
  • Pedometer. Nice feature, it's even gotten me to walk more. Allegedly you can publish your pedometer data to some Nike service, but I'm not particularly interested.
  • Sleep Mode is still there. Given that they removed so many other features (see below) I was glad to see the sleep mode was still there. It took a while to find it (it's in the Clock application.)
  • Also, When it's showing a clock face, it feels a bit like a pocket-watch. A very lightweight back-lit faux-analog pocket watch, but a pocket-watch nonetheless.
The bad:
  • Clumsy buttons. The use of more hard buttons on the iPod Nano feels like a bit of a cop-out, and even on my smaller hands, they're a bit hard to fumble with.
  • No video playback. Not even for video podcasts.  I don't mind much, they're clearly drawing a line between iPod and iPhone (or iPod touch.)
  • No games, no video camera, no contacts, no notes. I don't care about most of those, well, except for games, but I can always fall back on my phone if needs must.
  • Clumsy access to the dock connector. The headphone jack is so close to the dock connector that you have to remove the headphones before disconnecting the iPod. It's an irritating usability fumble, but understanding given the tiny form factor.
  • Tiny Engraving. I opted for engraving the device with my email address in case it gets lost. It's difficult to read the engraving without a magnifying glass. I'm guessing it's a 6-point font for a 20-character message.
The fact that they've removed features that might make someone more likely to purchase an iPod touch makes this feel much like the IBM PC Jr (a little nod to my fellow dinosaurs.) In some ways, it's like the PC Jr (replace chicklet keyboard with clumsy buttons.) Sure, they want you to buy an iPod Touch, but I'd rather have the lightweight device, and I already have a large bulky video-playing computer.

In short, this is great as a companion lower-power-consuming audio device.