Note to Subaru Marketing: Part Two
Dear Subaru Marketing:
Hello again! It's been two weeks since we last spoke, and I miss you already. As I mentioned then, I appreciate your efforts to market your vehicles in my area and your attempts at regional advertisements. Since that time you've introduced a new ad referring to I-95. While I'm no expert on geography, I'm quite sure that I-95 runs by New York City and just barely touches a corner of our state - the opposite corner from where you are advertising. Here, I've provided a helpful illustration:

I-95 is the red winding line. Rochester is located at the tip of the arrow.
Perhaps you are trying to save money by referring to a 1,927 mile long interstate highway that spans the entire east coast and running the ad in all those markets as "local". While this is ingenius, to reach Western New York you would likely want to reference I-90, or as we New Yorkers call it, the Thruway. It has the added bonus of stretching the entire continental US's northern states.
But those are really just semantics, after all you've discovered a way to make "regional" ads that span a huge swath of the country. Maybe you can subdivide the audience into similar niches beyond region by referencing very specific interests. Targetting such a niche would make the listener feel unique and passionate about your product. I suggest using in-jokes about Seinfeld, or maybe sharply dividing the audience and creating passionate responses by professing a love of puppies and babies.
Sincerely,
A member of your target audience
The Reverse Captcha
Tim O’Reilly posed an interesting side-question on the O’Reilly Radar blog in a write-up on Google’s Image Labeler.
This is an interesting variation on the Turing test, in which humans generate and grade tests that most humans can pass, but current computer programs cannot pass. Is there another variation in the future, in which computers generate and grade tests that computers can pass, but humans cannot pass?
I’m not sure of the applicability of such a test, but the idea is intriguing none the less. I would think that there are plenty of problems that would be too difficult to solve by a human, but they are based on the assumption that a time limit is given for the production of a solution. You could simply give a very difficult mathematical question with a subsecond solve time.
One possible example without a need for a time-limit: a picture with a randomly generated steganographic message embedded and a text field with no explanation - computers fill it with the embedded message, humans would likely try to ascertain the image’s contents as the answer. Sort of a reverse captcha. The assumption here is that the algorithm for generating the random message is truly random, or not able to be reverse-engineered and the next value guessed by a human (sort of like those random card shufflers in Vegas that aren’t supposed to be beatable).
In any case, the idea is thought provoking. Anyone else have examples?
Prototype based Tag Autocompletion
I've been working on a website that uses tags internally. The acts_as_taggable plugin has been great, but it only takes care of the ActiveRecord portion of tagging. One of the pieces of functionality I needed was to create an autocompleter for users to enter a list of tags on a record. Below is a pure copy-paste dump of my implementation. I'm not claiming it's good code or the right way top do things, but for those looking to use an autocompleter for a text field holding tags might find it useful. The implementation is close to that used by delicious in the tags field on posting new bookmarks.
Here's an example usage:
<label for="tags">Tags</label>
<%= text_field_tag 'tags', @tags.join(' '), :size => 80, :autocomplete => "off" %>
<div class="auto_complete" id="tags_auto_complete"></div> And the prototype-based class in javascript. You'll probably want to stick this in your application.js file if you're using Rails.
String.prototype.trim = function(){ return this.replace(/^\s+|\s+$/g,'') }
Autocompleter.Tag = Class.create();
Autocompleter.Tag.prototype = Object.extend(new Autocompleter.Base(), {
lastEdit: '',
currentTag: {},
initialize: function(element, update, array, options) {
this.baseInitialize(element, update, options);
this.options.array = array;
},
getCurrentTag: function() {
if(this.element.value == this.lastEdit) return true // no edit
if(this.element == '') return false
this.currentTag = {}
var tagArray=this.element.value.toLowerCase().split(' '), oldArray=this.lastEdit.toLowerCase().split(' '), currentTags = [], matched=false, t,o
for (t in tagArray) {
for (o in oldArray) {
if(typeof oldArray[o] == 'undefined') { oldArray.splice(o,1); break }
if(tagArray[t] == oldArray[o]) { matched = true; oldArray.splice(o,1); break; }
}
if(!matched) currentTags[currentTags.length] = t
matched=false
}
// more than one word changed... abort
if(currentTags.length > 1) {
// hideSuggestions(); TODO Fix this!
return false;
}
this.currentTag = { text:tagArray[currentTags[0]], index:currentTags[0] }
return true
},
getUpdatedChoices: function() {
this.updateChoices(this.options.selector(this));
},
onKeyPress: function(event) {
if(this.active)
switch(event.keyCode) {
case Event.KEY_TAB:
case Event.KEY_RETURN:
this.selectEntry();
Event.stop(event);
case Event.KEY_ESC:
this.hide();
this.active = false;
Event.stop(event);
return;
case Event.KEY_LEFT:
case Event.KEY_RIGHT:
return;
case Event.KEY_UP:
this.markPrevious();
this.render();
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
return;
case Event.KEY_DOWN:
this.markNext();
this.render();
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
return;
}
else
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
(navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
this.lastEdit = this.element.value;
this.changed = true;
this.hasFocus = true;
if(this.observer) clearTimeout(this.observer);
this.observer =
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
},
updateElement: function(selectedElement) {
if (this.options.updateElement) {
this.options.updateElement(selectedElement);
return;
}
var value = '';
if (this.options.select) {
var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
} else
value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
var lastTokenPos = this.findLastToken();
if (lastTokenPos != -1) {
var newValue = this.element.value.substr(0, lastTokenPos + 1);
var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
if (whitespace)
newValue += whitespace[0];
this.element.value = newValue + value;
} else {
tagArray = this.element.value.trim().split(' ')
if (!this.getCurrentTag()) return; // We had a problem getting the current tag, just return?
tagArray[this.currentTag.index] = value;
this.element.value = tagArray.join(' ');
}
this.element.focus();
if (this.options.afterUpdateElement)
this.options.afterUpdateElement(this.element, selectedElement);
},
setOptions: function(options) {
this.options = Object.extend({
choices: 10,
partialSearch: true,
partialChars: 2,
ignoreCase: true,
fullSearch: false,
selector: function(instance) {
var ret = []; // Beginning matches
var partial = []; // Inside matches
var entry = instance.getToken();
entry = entry.trim().split(' ').pop()
var count = 0;
for (var i = 0; i < instance.options.array.length &&
ret.length < instance.options.choices ; i++) {
var elem = instance.options.array[i];
var foundPos = instance.options.ignoreCase ?
elem.toLowerCase().indexOf(entry.toLowerCase()) :
elem.indexOf(entry);
while (foundPos != -1) {
if (foundPos == 0 && elem.length != entry.length) {
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
elem.substr(entry.length) + "</li>");
break;
} else if (entry.length >= instance.options.partialChars &&
instance.options.partialSearch && foundPos != -1) {
if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
foundPos + entry.length) + "</li>");
break;
}
}
foundPos = instance.options.ignoreCase ?
elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
elem.indexOf(entry, foundPos + 1);
}
}
if (partial.length)
ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
return "<ul>" + ret.join('') + "</ul>";
}
}, options || {});
}
});
Note to Subaru Marketing
Dear Subaru Marketing:
I appreciate your efforts to market your vehicles in my area; and I also appreciate your effort in creating regional specific advertisements - it lends a nice local feel to them. Bravo! However, you may want to actually make sure that the region you're airing the commercials within actually makes sense.
There are other cities in New York state besides New York city. Upstate / Western New York is not referred to as the tri-state area. So, your slogan to tell use that we "try everything" and maybe that's why we're the "try-state" area is cute as buttons and puppy dogs - and horribly wrong. The area you're referring to is a 400 mile, 7-hour drive across nowheresville from us. Perhaps you could create one specific for our area: Since we are "Western" New York you may want to mention cowboys and indians. Or perhaps use the title "Finger Lakes region" and talk about rude gestures.
Sincerely,
A member of your target audience