Transitioning from a DIV to Table-based layout. On purpose.
I’ve been working on a site that uses a App-model style layout;
3 columns, like an Email client.

It works using Divs laid out with CSS, and generally works really well.
It even loads well on older version of Netscape, properly degrading to run, even if it’s not as pretty.

The more I look at it, though, the more I wonder if there is more I could do.
If dropping support for ancient browsers is really necessary at all.
Given that it’s a simple 3 column site, my initial thought was to experiment with frames. While these work, dataURI+frames aren’t as well supported as I’d like, and the experience of frames on lynx is not ideal.
What about Tables?
Tables have gotten a bad rap in recent years.
It is generally recommended that we avoid tables, and use divs for layout. But why?
There are few traditional arguments that are given against tables:
- HTML should be semantic
- Screen Readers can’t handle Tables
- CSS lets you just change the one CSS file, rather than every page
- Table Layouts are only used by people who can’t do divs properly
It’s often argued that HTML should be semantic where possible, and to split the presentation (CSS) from the layout (HTML). In practice, however, this often doesn’t work out. To make various tricks work right, developers are often creating a wide variety of presentational divs anyway, for wrappers and other non-semantic content.
<div class="region region-content">
<div id="block-system-main" class="block block-system">
<div class="content">
<!– Actual page content here –>
</div>
</div>
</div>
You could certainly argue that we’re moving in a more semantic direction, and HTML5 certainly moves in that direction, but we’re still a long way away from there.
Combine that with other work arounds that are being done to support non-performant browsers
[if IE 6]>
and it’s hard to justify that a simple table is much worse.
Modern screen readers are actually fairly decently at handling tables. They’ve improved in the last 10 years since we started using CSS.
Additionally, you can mark a table as presentational, so that it can be interpreted as such using role=”presentation”.
JAWS, WindowEyes, and NVDA will all do the right thing.
If I were manually editing 100s of files, this might be a valid concern – But like many modern developers, I’m writing web applications designed to run with a templating system – Python, Ruby, even PHP have libraries which make this trivial. I can change my structure in a single location, and have it automatically apply on every pageload.
I can sympathize with this view- There’s a lot of people who get stuck in CSS land, and resort to a “Screw it, I’ll use a table” attitude, so I can see where it comes from. But Google and Twitter are using tables as part of their layouts, and I’m sure they have someone there who knows what they’re doing
In my case, I have the layout working properly in CSS, but I’m deliberately redoing it, spending extra time, to gain the broad-compatibility that a table-based layout brings.
Some things are easier, some things are harder, but it’s a valid choice in designing a site.
But CSS is really useful!
It sure is!
And I’m in favor of using CSS, all over the place!
Gradients are better than images, rotation is amazing, and changing elements through properties is useful.
CSS has it’s warts (Templating would be nice..), but it’s a great way to explain how elements should look. And divs are often useful for layout, too.
But non-tabular use of a tables is probably OK, as part of a well-balanced breakfast.
Using Mechanical Turk to build a library of Grocery Cards

Earlier this year, I wanted to add the ability to generate and display barcodes as part the Savingstar iPhone app, similar to Loyalty card wallets.
We’re already storing loyalty card numbers of over a million users in order to process their coupons – It seemed like regenerating these into scannable barcodes should be simple enough.
For stores with a scanner that can read a phone (an increasing number), this is a lot more convenient.
Looking over the various card ranges, they fell into a few formats- EAN-13, UPC, etc.
Luckily, there is a library which can easily write these, and easy to use python bindings in order to use them.

After a few trials however, I ran into a small issue-
The numbers in the barcode don’t always match up with the printed card number!
Often there is a prefix/postfix on the number, in addition to the checkdigits for UPCs.
After thinking about it for a while, I realized I could reverse engineer the formats, but I’d need cards. Lots of cards.
I turned to Mechanical Turk.
Getting Cards
I posted several jobs, each time asking people to mail me loyalty cards.
Because I needed to match the cards to a something digital to approve/deny them on Mechanical Turk, generated hundreds of random strings, and assigned one to each user.
I then asked each user to then write their particular random string on the envelope; When I received the envelope, I could match it up and give them credit for the job.
And we got cards! Hundreds of beautiful cards, dozens of cards in each of the ranges I needed.
People from around the US and Canada send cards from every major chain in the US.
The biggest downsize to this method is that there is a built in lag.. So even after I had enough cards for each certain chains, and pulled the listing, I still received loads of cards that had already been mailed

Mount KVM images on Linux
Just a quick little script I use once in a while, which I need to mount my KVM images, to access the files manually.
The way it works is to calculate where the data starts, then simply pass that as a paramter to the mount command.
simple, but very useful!
#!/bin/bash
if [ $# -lt 2 ]
then
echo "Usage $0 image mountpoint"
exit
fi
IMAGEFILE=$1
MOUNTPOINT=$2
SECTORSIZE=$(parted -s $IMAGEFILE unit s print | grep "Sector size" | awk -F": " {'print $2'} | awk -F"B/" {'print $1'})
OFFSETBLOCKS=$(parted -s $IMAGEFILE unit s print | grep "^ 3" | awk {'print $2'} | awk -Fs {'print $1'})
OFFSETSECTORS=$(echo "$SECTORSIZE * $OFFSETBLOCKS" | bc)
mount -o loop,offset=$OFFSETSECTORS $IMAGEFILE $MOUNTPOINT
How to write a simple ajax game loop in Javascript
A friend of mine from the Java world has been trying to get into JS lately.
He understood the basics of how JS works – Functions look (on the outside) superficially similar to Java, it has standard loops/variables, etc.
Canvas functions are very straight forward- The part that confused him was “How do I create a game loop”, and “How do I update the page based on JSON responses from my server?”
Luckily, both of those are REALLY easy in Javascript!
I’m certainly not a Javascript expert, but I put together a simple demonstration, with an over-the-top number of comments.

We’ll start with our very simple HTML page.
Anyone who’s done even “Hello World” should understand most of what we’re doing here.
We’re loading our JS, then three divs – Header, Game, and Footer.
<html>
<head>
<title>This is an example</title>
<!--
Include are our JS scripts. The "defer" line just tells it to run this after the page loads.
It's not actually needed, but it makes the initial load faster
We're loading in jQuery via a CDN, but copying it locally would work just as well.
This is just one less file to deal with.
-->
<script defer src="http://code.jquery.com/jquery.js"></script>
<script defer src="exampleCode.js"></script>
</head>
<!--
In the body (below), define the game however you want it to look.
I'm using 3 divs, just for the sake of showing we're only showing/changing the game div.
There's nothing special about this setup.
-->
<body>
<div id="Intro">
This is the beginning of the game.
</div>
<div id="Game">
The GAME GOES HERE.
</div>
<div id="Footer">
This is the bottom.
</div>
</body>
Now this is calling our JS, exampleCode.js –
That’s also very simple. We’re just going to do two “game loops” to update the page.
One of which updates the color, and the other uses an AJAX request
// This function does the logic of updating our game div.
// Obviously, in a real game, this would be more interesting.
function ChangeColor()
{
// in jQuery, the $ character gives you the root jQuery object.
// You can use the $(FOO) notation gives you a reference to any element
// Any element you want. It's just like a CSS selector.
// So-
// $('#foo') gives you a HTML element with id="foo"
// $('.foo') gives you every element with the class="foo" (loop through them)
// The next line creates an array of random colors.
bgcolorlist= Array("#DFDFFF", "#FFFFBF", "#80FF80", "#EAEAFF", "#C9FFA8", "#F7F7F7", "#FFFFFF", "#DDDD00");
// Now, Randomly choose one, by picking a random number (between 1 and arraysize), and then get that array element.
randombgcolor = bgcolorlist[Math.floor(Math.random()*bgcolorlist.length)];
// Now, in jQuery, you can set a CSS property, by using the .css function.
// I'm using it to set the background for the HTML element #Game.
$("#Game").css('background',randombgcolor);
}
// Let's try a slightly more interesting function. We'll add an external call.
function UpdateTime()
{
// Now, let's do something Ajaxy-
// There is a URL endpoint for Tavern that gives us the server status, including the time.
// We can use that to display the current time on our page.
// The $.ajax() function doesn't return the JSON.. It RUNS the success value as a function.
// That function will save the variable for us.
// Normally, we could use $.get(), or $.getJSON(), but these just call $.ajax() under the hood.
// By calling $.ajax() directly, we can set more options.
// set the result variable ahead of time, so it's scope isn't only inside the function.
var result;
// make the AJAX call
$.ajax({
// async defaults to True- This means that the result will wait for a return before running anything else.
async: false,
// Setting the dataType to JSON is a convenience. It automatically parses it into a JSON object for us.
dataType:"json",
// This is the URL we're pulling from. I set this up to be accessible from any domain.
// Normally, you can only make requests to the SAME DOMAIN as the HTML runs from.
// But you can bypass this with a header (Access-Control-Allow-Origin), which I've set for you.
url: "http://gettavern.com:8090/status",
// This is just our super-simple function that gets called on success.
// ALL it's doing is setting the result back to that variable we declared before.
// Normally, in JS, you'd just do whatever it is you actually needed with your drawing/etc, right in the inline function..
// Or have it call a longer function, that's defined normally (like the one we're in!)
// But in this case, I wanted to a) illustrate Inline functions.
// and b) make it short
success: function(data) {
result = data;
}
});
// Now, make that into something printable. Notice how we pull values out of the JS call as if it were a dictionary/hashmap?
timeStampString = "The current timestamp is " + result['timestamp'] + ".";
// Now, let's DO something with this data-
// create a new javascript Date object based on the timestamp
// multiplied by 1000 so that the argument is in milliseconds, not seconds
var date = new Date(result['timestamp']*1000);
// hours part from the timestamp
var hours = date.getHours();
// minutes part from the timestamp
var minutes = date.getMinutes();
// seconds part from the timestamp
var seconds = date.getSeconds();
// will display time in 10:30:23 format
var formattedTime = "
This is equal to " + hours + ":" + minutes + ":" + seconds;
// This redefines the HTML inside the game object to whatever we pass it. In this case, it's the time.
// Get the Game's DOM object
// This gets the instance of the Game, just like we were in the first function.
game = $("#Game");
// Replace the Game's HTML.
// Just like used the background function in the first example (above), here we're overwriting the HTML that's in the Game object.
// Everytime this runs, everthing that is in the game div is erased, and replaced with whatever string we pass.
// In this case, we're passing in our two time-based strings we just created.
game.html(timeStampString + formattedTime);
}
// Run the code above, every 500ms
self.setInterval("ChangeColor()",500);
self.setInterval("UpdateTime()",1000);
You can see this in action at jsfiddle.
It’s all very very straightforward code, but it occured to me that it might be useful to others, so I’ve archived it here for anyone who finds it
iStat Menus demotes user’s licenses with sneaky upgrade
For the last few years, I’ve been using a cute little tool called iStat menus to graph my CPU/Memory in my address bar.
It doesn’t do that much, but it works, it stays out of my way, and the UI is better than the nearest free alternative.
![]()
I don’t think about the tool very much – Once in a while I’ll glance up when the system is running slow, or use the clock function to see the time in multiple time-zones.
Every so often, there’s a new version available, and the software prompts me to upgrade –


I generally go ahead and agree to the change, and go about my business.
Although my 1999 self would cry to hear me say it, I don’t always read through the changelogs-
We get them everywhere now – On my Android Phone (Yep, go ahead, update), iPhone (Sure. Update), OSX (OK, add the new release), etc.
In my professional life managing servers, I review the changes, weigh the pros and cons, and decide if I want to upgrade – But for a simple Menu app, that seems overkill.
This week, iStat released another upgrade, and once more I upgraded without really paying much attention.
I probably should have, but realistically, I don’t think most users will.

As you can see in the screenshot however, this upgrade to iStat Pro 4 is a paid upgrade.
Accepting this upgrade downgraded my paid software to 14-day trial status.

I’m not the only one who felt that wasn’t cool.
iStat is offering upgrade pricing ($9), and the app isn’t that expensive in and of itself, but the entire process felt very sketchy.[1]
Users are used to accepting in-app upgrades, and switching one out for a paid-upgrade feels very disingenuous, regardless of their justifications.
Bjango (the creators) could have handled things differently,by setting the “Remind me Later” Dialog option to be the blue/rightmost option, or even installing as a different name in the Applications folder, so you could still easily fire up v3.
Admittedly, It’s a minor issue, $9 isn’t much, and I don’t begrudge an indie shop trying to raise money for a new version and stand in business, but the sense of fairplay has been violated- It’s one more reason for me to try to avoid buying non App Store applications in the future.
For anyone else who runs into this, You can uninstall iStat 4 using the menu item-

And then reinstall Version 3.
[1]For what it’s worth, I did end up buying the upgrade. The UI is a bit nicer.
This isn’t about the $9. It’s about the process.
Fun with Framesets in 2012
One of the things I’ve noticed in web development is a trend toward avoiding older technologies.
There’s often a good reason for this – Newer replacements allow more flexibility, work more reliably, and are better supported.
But that doesn’t mean we should always abandon old friends – In this case, I mean the humble HTML frame.
Although deprecated in HTML5, there’s still fun to be had.
But aren’t frames evil?
One of the biggest criticisms of frames is that they aren’t reliably accessed – Their very nature allow search-engines (or users) to link to one specific frame, rather than to the frameset container you’re wrapping them with.
It turns out, we have a weapon against this, which has been around since 1998- The DataURI.
Rather than linking to a specific file, using the datauri, we can embed our frame elements directly INSIDE the original page.
They can be generated at the same time, with the rest of your code, and stored directly.
This eliminates the idea of a separate page that might ever accidentally get linked to.
<frameset cols="30%,*"> <frame src="data:text/html;base64,CjxodG1sPgogICAgPGhlYWQ+CiAgICAgICAgPHRpdGxlPkZyYW1lIE1lbnU8L3RpdGxlPgogICAgICAgIDxiYXNlIHRhcmdldD0iY29udGVudCI+CiAgICA8L2hlYWQ+CiAgICA8Ym9keSBiZ2NvbG9yPSIjMDA5OUREIj4KICAgICAgICA8YSBocmVmPSJodHRwOi8vd3d3LnlhaG9vLmNvbSIgY2xhc3M9Im1lbnUiPllhaG9vLmNvbTwvYT48YnIgLz4KICAgICAgICA8YSBocmVmPSJodHRwOi8vd3d3Lmdvb2dsZS5jb20iIGNsYXNzPSJtZW51Ij5Hb29nbGUuY29tPC9hPjxiciAvPgogICAgICAgIDxociAvPgogICAgICAgIDxhIGhyZWY9Imh0dHA6Ly93d3cuZWJheS5jb20iIGNsYXNzPSJtZW51Ij5FYmF5LmNvbTwvYT48YnIgLz4KICAgICAgICA8YSBocmVmPSJodHRwOi8vd3d3LmZhbmRhbmdvLmNvbSIgY2xhc3M9Im1lbnUiPkZhbmRhbmdvLmNvbTwvYT48YnIgLz4KICAgIDwvYm9keT4KPC9odG1sPgo="> <frame src="data:text/html;base64,CjxodG1sPgogICAgPGhlYWQ+CiAgICAgICAgPHRpdGxlPkZyYW1lIENvbnRlbnQ8L3RpdGxlPgogICAgPC9oZWFkPgogICAgPGJvZHkgYmdjb2xvcj0iI0RERERERCI+CiAgICAgICAgPHA+SGVyZSdzIHdoYXQgaXQgd291bGQgbG9vayBsaWtlQSBnb29kIHJ1bGUgb2YgdGh1bWIgaXMgdG8gY2FsbCB0aGUgcGFnZSB3aGljaCBjb250YWlucyB0aGlzIGZyYW1lIGluZm9ybWF0aW9uICJpbmRleC5odG1sIiBiZWNhdXNlIHRoYXQgaXMgdHlwaWNhbGx5IGEgc2l0ZSdzIG1haW4gcGFnZS4KICAgICAgICA8cD5BIGdvb2QgcnVsZSBvZiB0aHVtYiBpcyB0byBjYWxsIHRoZSBwYWdlIHdoaWNoIGNvbnRhaW5zIHRoaXMgZnJhbWUgaW5mb3JtYXRpb24gImluZGV4Lmh0bWwiIGJlY2F1c2UgdGhhdCBpcyB0eXBpY2FsbHkgYSBzaXRlJ3MgbWFpbiBwYWdlLjwvcD4KICAgICAgICA8cD5BIGdvb2QgcnVsZSBvZiB0aHVtYiBpcyB0byBjYWxsIHRoZSBwYWdlIHdoaWNoIGNvbnRhaW5zIHRoaXMgZnJhbWUgaW5mb3JtYXRpb24gImluZGV4Lmh0bWwiIGJlY2F1c2UgdGhhdCBpcyB0eXBpY2FsbHkgYSBzaXRlJ3MgbWFpbiBwYWdlLjwvcD4KICAgICAgICA8cD5BIGdvb2QgcnVsZSBvZiB0aHVtYiBpcyB0byBjYWxsIHRoZSBwYWdlIHdoaWNoIGNvbnRhaW5zIHRoaXMgZnJhbWUgaW5mb3JtYXRpb24gImluZGV4Lmh0bWwiIGJlY2F1c2UgdGhhdCBpcyB0eXBpY2FsbHkgYSBzaXRlJ3MgbWFpbiBwYWdlLjwvcD4KICAgICAgICA8cD5BIGdvb2QgcnVsZSBvZiB0aHVtYiBpcyB0byBjYWxsIHRoZSBwYWdlIHdoaWNoIGNvbnRhaW5zIHRoaXMgZnJhbWUgaW5mb3JtYXRpb24gImluZGV4Lmh0bWwiIGJlY2F1c2UgdGhhdCBpcyB0eXBpY2FsbHkgYSBzaXRlJ3MgbWFpbiBwYWdlLjwvcD4KICAgICAgICA8cD5BIGdvb2QgcnVsZSBvZiB0aHVtYiBpcyB0byBjYWxsIHRoZSBwYWdlIHdoaWNoIGNvbnRhaW5zIHRoaXMgZnJhbWUgaW5mb3JtYXRpb24gImluZGV4Lmh0bWwiIGJlY2F1c2UgdGhhdCBpcyB0eXBpY2FsbHkgYSBzaXRlJ3MgbWFpbiBwYWdlLjwvcD4KICAgICAgICA8cD5IZXJlJ3Mgd2hhdCBpdCB3b3VsZCBsb29rIGxpa2VBIGdvb2QgcnVsZSBvZiB0aHVtYiBpcyB0byBjYWxsIHRoZSBwYWdlIHdoaWNoIGNvbnRhaW5zIHRoaXMgZnJhbWUgaW5mb3JtYXRpb24gImluZGV4Lmh0bWwiIGJlY2F1c2UgdGhhdCBpcyB0eXBpY2FsbHkgYSBzaXRlJ3MgbWFpbiBwYWdlLgogICAgICAgIDxwPkEgZ29vZCBydWxlIG9mIHRodW1iIGlzIHRvIGNhbGwgdGhlIHBhZ2Ugd2hpY2ggY29udGFpbnMgdGhpcyBmcmFtZSBpbmZvcm1hdGlvbiAiaW5kZXguaHRtbCIgYmVjYXVzZSB0aGF0IGlzIHR5cGljYWxseSBhIHNpdGUncyBtYWluIHBhZ2UuPC9wPgogICAgICAgIDxwPkEgZ29vZCBydWxlIG9mIHRodW1iIGlzIHRvIGNhbGwgdGhlIHBhZ2Ugd2hpY2ggY29udGFpbnMgdGhpcyBmcmFtZSBpbmZvcm1hdGlvbiAiaW5kZXguaHRtbCIgYmVjYXVzZSB0aGF0IGlzIHR5cGljYWxseSBhIHNpdGUncyBtYWluIHBhZ2UuPC9wPgogICAgICAgIDxwPkEgZ29vZCBydWxlIG9mIHRodW1iIGlzIHRvIGNhbGwgdGhlIHBhZ2Ugd2hpY2ggY29udGFpbnMgdGhpcyBmcmFtZSBpbmZvcm1hdGlvbiAiaW5kZXguaHRtbCIgYmVjYXVzZSB0aGF0IGlzIHR5cGljYWxseSBhIHNpdGUncyBtYWluIHBhZ2UuPC9wPgogICAgICAgIDxwPkEgZ29vZCBydWxlIG9mIHRodW1iIGlzIHRvIGNhbGwgdGhlIHBhZ2Ugd2hpY2ggY29udGFpbnMgdGhpcyBmcmFtZSBpbmZvcm1hdGlvbiAiaW5kZXguaHRtbCIgYmVjYXVzZSB0aGF0IGlzIHR5cGljYWxseSBhIHNpdGUncyBtYWluIHBhZ2UuPC9wPgogICAgICAgIDxwPkEgZ29vZCBydWxlIG9mIHRodW1iIGlzIHRvIGNhbGwgdGhlIHBhZ2Ugd2hpY2ggY29udGFpbnMgdGhpcyBmcmFtZSBpbmZvcm1hdGlvbiAiaW5kZXguaHRtbCIgYmVjYXVzZSB0aGF0IGlzIHR5cGljYWxseSBhIHNpdGUncyBtYWluIHBhZ2UuPC9wPgogICAgICAgIDxwPkhlcmUncyB3aGF0IGl0IHdvdWxkIGxvb2sgbGlrZUEgZ29vZCBydWxlIG9mIHRodW1iIGlzIHRvIGNhbGwgdGhlIHBhZ2Ugd2hpY2ggY29udGFpbnMgdGhpcyBmcmFtZSBpbmZvcm1hdGlvbiAiaW5kZXguaHRtbCIgYmVjYXVzZSB0aGF0IGlzIHR5cGljYWxseSBhIHNpdGUncyBtYWluIHBhZ2UuCiAgICAgICAgPHA+QSBnb29kIHJ1bGUgb2YgdGh1bWIgaXMgdG8gY2FsbCB0aGUgcGFnZSB3aGljaCBjb250YWlucyB0aGlzIGZyYW1lIGluZm9ybWF0aW9uICJpbmRleC5odG1sIiBiZWNhdXNlIHRoYXQgaXMgdHlwaWNhbGx5IGEgc2l0ZSdzIG1haW4gcGFnZS48L3A+CiAgICAgICAgPHA+QSBnb29kIHJ1bGUgb2YgdGh1bWIgaXMgdG8gY2FsbCB0aGUgcGFnZSB3aGljaCBjb250YWlucyB0aGlzIGZyYW1lIGluZm9ybWF0aW9uICJpbmRleC5odG1sIiBiZWNhdXNlIHRoYXQgaXMgdHlwaWNhbGx5IGEgc2l0ZSdzIG1haW4gcGFnZS48L3A+CiAgICAgICAgPHA+QSBnb29kIHJ1bGUgb2YgdGh1bWIgaXMgdG8gY2FsbCB0aGUgcGFnZSB3aGljaCBjb250YWlucyB0aGlzIGZyYW1lIGluZm9ybWF0aW9uICJpbmRleC5odG1sIiBiZWNhdXNlIHRoYXQgaXMgdHlwaWNhbGx5IGEgc2l0ZSdzIG1haW4gcGFnZS48L3A+CiAgICAgICAgPHA+QSBnb29kIHJ1bGUgb2YgdGh1bWIgaXMgdG8gY2FsbCB0aGUgcGFnZSB3aGljaCBjb250YWlucyB0aGlzIGZyYW1lIGluZm9ybWF0aW9uICJpbmRleC5odG1sIiBiZWNhdXNlIHRoYXQgaXMgdHlwaWNhbGx5IGEgc2l0ZSdzIG1haW4gcGFnZS48L3A+CiAgICAgICAgPHA+QSBnb29kIHJ1bGUgb2YgdGh1bWIgaXMgdG8gY2FsbCB0aGUgcGFnZSB3aGljaCBjb250YWlucyB0aGlzIGZyYW1lIGluZm9ybWF0aW9uICJpbmRleC5odG1sIiBiZWNhdXNlIHRoYXQgaXMgdHlwaWNhbGx5IGEgc2l0ZSdzIG1haW4gcGFnZS48L3A+CiAgICA8L2JvZHk+CjwvaHRtbD4KCg=="> </frameset>
If your browser supports the “data:text/html;charset=UTF-8,” type (webkit seems to like this better than others) you can make this even easier, and embed your normal HTML, straight into the original page.
This will work somewhat similarly to a resizable-div, but using 1998-level technology.
<frameset cols="30%,*"> <frame src="data:text/html;charset=UTF-8,<html> <head> <title>Frame Content</title> </head> <body bgcolor='#DDDDDD'> <p>Here's what it would look likeA good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page. <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p> <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p> <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p> <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p> <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p> <p>Here's what it would look likeA good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page. <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p> <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p> <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p> <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p> <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p> <p>Here's what it would look likeA good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page. <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p> <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p> <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p> <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p> <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p> </body> </html>"> <frame src="data:text/html;charset=UTF-8, <html> <head> <title>Frame Menu</title> <base target='content'> </head> <body bgcolor='#0099DD'> <a href='http://www.yahoo.com' class='menu'>Yahoo.com</a><br /> <a href='http://www.google.com' class='menu'>Google.com</a><br /> <hr /> <a href='http://www.ebay.com' class='menu'>Ebay.com</a><br /> <a href='http://www.fandango.com' class='menu'>Fandango.com</a><br /> </body> </html>"> </frameset>
Why would I use this?
Honestly, I don’t think that anyone should
But it does have certain advantages over “Splitter” style javascript -
- It’ll work without JS enabled
- It’s compatible with old browsers
- The browser does the work of resizing, rather than requiring it to happen in JS
In all, it’s a fun hack, and I figured it might appeal to other people who haven’t forgotten about some of the older layers still with us in our browsers.
Of course, while it might work in Netscape 3.0, like anything fun, it doesn’t work in older versions of IE.
LinkedIn’s new Endorsement system is Viral SPAM. And that’s why it works.
Late last month, in an effort to increase user-engagement, LinkedIn launched a new feature which lets you ‘endorse’ your connections with one click.
This is intended to be a short-hand way of recommending people, designed to get the lazy among us to attest to the skills of their former coworkers. It might get clicks, but they’re meaningless noise.
It starts
The process starts when one of your colleges ‘endorses’ you using the new system.
This sends an email to your inbox, which looks vaguely interesting.

You click to investigate, and you’re presented with a list of your friends, asking if they have certain skills.

The UI makes it easy to mass-endorse your friends without even reading the dialogs, or to endorse each person with a single click.
After endorsing a skill, the dialog replaces them with with another skill for another one of your connections.
Endorsing them, however, just perpetuates the cycle. They will then receive the same email you did.
They’ll click and log in, thinking that someone might have had something meaningful to say about them, but be presented with the annoying Viral mechanism that caught you earlier.
Nowhere to run
While I appreciate that LinkedIn is trying to drive people to the site, encouraging contact-spamming seems one of the cheesiest ways to do so.
There is currently no way to disable endorsements entirely, and Unendorsing is a tedious process, requiring you to go to each profile manually.
I understand that LinkedIn is trying to ensure that LinkedIn is a tool people use every day, but this makes me more likely to simply killfile them in the future.
Pulling the Mixergy archive locally

Mixergy.com is a great resource for interviews with various entrepreneurs, both famous and up-and-coming.
I generally enjoy listening to the interviews, but I recently I’ve begun trying to exercise more, and I’ve been enjoying listening to his podcast while I walk the trails.
Unfortunately, the podcasts are only available for a limited period of time, before they become premium only.
I don’t mind paying in order to download the back archives, but it’s not a straight fee-for-product transaction. At least when I last looked at it, you could only download individual interview files if you found them, one page at a time.
That’s great if you’re trying to download a single interview and listen to it, but if you want them all on your iPod to choose while walking or driving, it’s less than ideal.
Being an command line guy, I realized this should be a simple problem to solve using some bash scripting. I’m sure I could have done it in Python just as easily, but since I’m in the terminal anyway, Bash is a great Go-To solution to problems
Logging in
Mixergy uses cookies for authentication, storing a login token, and then checking for it when you try to download a file.
This makes a lot of sense, and is straightforward to work with.
I logged in using Firefox, and exported out my cookies for mixergy.com, then saved them out to a file using a Firefox plugin.
I could then use this for the next set of requests.
Acquire List of Interviews
Since I couldn’t find a list of all the interviews on one page, I had to crawl backward on the news blog, harvesting each link.
I noticed that Mixergy always linked each interview in it’s own page, with “Read More” as the anchor text.
I tested pulling these links in, and it seemed to work reliably.
# Generate list of interviews
curl -L –cookies cookies http://mixergy.com/interviews/page/1/ | grep “Read more” | awk -F\” {‘print $4′}
This seemed to give me the list of interview-specific pages pretty well, so I iterated this out to the rest of the pages in the archive, moving through each page of the search results.
I added a sleep in between requests to avoid hammering the server.
# Pull ALL the Interview URLS.
for i in `seq 1 62`; do curl -L –cookies cookies http://mixergy.com/interviews/page/$i/ | grep “Read more” | awk -F\” {‘print $4′} >> interviewpages; sleep 1; done
I then used very similiar logic to extract out the specific pages for the classes.
#Do the same for the classes.
for i in `seq 1 6`; do curl -L –cookies cookies http://mixergy.com/premium/page/$i/ | grep “<h2><span>”| awk -F”a href=” {‘print $2′} | awk -F\” {‘print $2′} >> classes; sleep 1; done
Looking through these, I now had a list of URLs, each of which contained the text of the interview, and a link to the MP3 version.
Acquire Each Audio File
At this point, I just had to extract the links to the mp3s.
I tested with a single page-
#Generate list of MP3s
curl -L –cookies cookies http://mixergy.com/eddy-lu-grubwithus-interview/ | grep mp3 | awk -F “a href” {‘print $2′} | awk -F\” {‘print $2′}
This seemed to work – It gave me a URL to a single MP3.
I then rolled this through each of the single-interview pages I had downloaded before, to find the URLs of all MP3s.
#Get the MP3 list
for i in `cat interviewpages`; do sleep 1; curl -L –cookie cookies $i | grep mp3 | awk -F “a href” {‘print $2′} | awk -F\” {‘print $2′} >> interviewmp3 ;done
This gave me a list “interviewmp3″ which contained a direct link to each file.
From here, it was a simple matter to loop through and download each one.
# Retrieve all MP3s.
for i in `cat interviewmp3`; do sleep 1; wget $i; done
And Success! I downloaded hundreds of startup interviews, and can load them to whatever devices I choose, and listen to them whenever I want.

PSA: Avoid information leakage via iTunes sharing of voice memos
While waiting for my flight home from SFO, I tooling around in iTunes to pass the time.
As I my flight became increasingly delayed, I started exploring the libraries of some of the other passengers, as they passed in and out of the airport.
One thing that I noticed on quite a few laptops as they passed by, is that people are sharing Voice Memos in iTunes — And I doubt that they intend to me.

I’m not the sort to listen through people’s personal voice memos, but further testing with some of my machines show that iTunes does generally attempt to block these files from streaming, even though it does display them.
I can see how this might cause problems if the file were named something like “Shocking Confession of infidelity”, but should otherwise be fairly benign.
We can take advantage of this, in order to remove them from the Shared list-
iTunes is able to detect these files, and by default will set the “Media Type” field to be “Voice Memos”, rather than “Music”.

What I’ve done to be avoid any potential issue, however, is to uncheck “Share Entire Library”, and only allow iTunes to explicitly share “Music”.

Why Yes, I am a small business.
“When someone asks you if you’re a small business, you say ‘YES’! – Winston Zeddmore, after talking to his ISP
Over the last few years, I’ve noticed a trend in the way services target consumers-
More often than not, they complicate and complexify what ought to be be very simple processes.
You often see this in the form of “Wizards”, which try to walk you through things, one step at a time.
While I appreciate the intent to make things more straightforward, these systems risk having the opposite of their intended effect. Their utility comes from reducing the number of things that people need to think about at once, but the tradeoff is that you need to approach things in the manner they suggest.
One example of where this goes awry is investigating Verizon for cellphone service.
Verizon forces visitors to their site to choose a model and quantity of telephones, as well as entering your street address and zip code, before they’re willing to show you plan pricing.
While this may match some user-stories, this forces a specific model of shopping-
I can’t easily compare the price per line before deciding how many lines I’m interested in, and I can’t quickly compare their prices against other services.

After making a best-guess at the number of phones, and lying about the phone I want – I want pricing for the iPhone 5, which was not yet available on their site – I am able to see some pricing information.
Once more, however, I am presented with an interface which makes things more complicated.
It doesn’t give me options about paying for unlimited SMS or dividing up my minutes.
Further, I need to horizontally scroll right and left to see all of the available options.
It manages to use the entire page to say very little.

Verizon requires horizontal scrolling to see all of the available options.
The pricing page for their Small Businesses, on the other hand, is far more straightforward.

The chart gives me all of the available options, very quickly and easily.
I can choose a number of lines, dataplans, and features. The pricing is straightforward and easy to follow.
This is widespread
This annoyance isn’t limited to Verizon-
Let’s look, for example, at Comcast Internet pricing.
On their normal consumer page, we’re greeted with the following rates-

Annoying fake chart- Trial offers.
It seems somewhat straight-forward, until you see that these are prices that only last for 6 months. The price that it will convert to is available elsewhere on the site, but you need to dig for it, and cross reference the plan price.
Compare this to the Comcast Xfinity for Business pricing-

Small Businesses get nonsense, straightforward pricing.
They seem to fall into a near mythical perfect category – They are able to avoid the wizards, trial offers, bandwidth caps, and stupidity that you see with consumer offers, but they haven’t grown into the “Call for Pricing” category of enterprise offers.
The pricing is generally comparable or cheaper over the long term, although there are fewer teaser-rates and promotions.
It’s straight-forward, no bullshit pricing – Fee for Service.
Why can’t everything work this way?

