From Facebook Developer Wiki
This is a re-implementation of Facebook's typeahead selectors used in various places around the site, for example the Religion field on http://www.facebook.com/editprofile.php. We wanted to make sure FBJS was powerful enough to let developers create great rich Javascript widgets, and this is a good example of that. Of course using this code is silly, since we already allow you to create typeahead fields with FBML using the command at http://wiki.developers.facebook.com/index.php/Fb:friend-selector.
If you'd like a typeahead widget for use with an AJAX data source, look at FBJS/Examples/Typeahead/AJAX.
This is the CSS given in the example. It works fine in Firefox, but fails in Safari.
.th_list {
margin-top: -1px;
}
This is the CSS that works in Safari.
.th_list {
margin-top: 22px;
}
What to do?
The CSS is returning errors. 548871286 15:59, 16 October 2007 (PDT)
<script><!--
var autocomplete = ['Facebook','foo','bar','FBJS','Safari','Firefox','Internet Explorer','Opera','Mario','Yoshi'];
//--></script>
<div>
<input id="typeahead" class="inputtext th_placeholder" value="Type something here..." onfocus="new typeahead(document.getElementById('typeahead'), autocomplete)" /><br />
</div>
<script><!--
// A very basic typeahead object
function typeahead(obj, options) {
this.obj = obj;
// Setup the events we're listening to
this.obj.purgeEventListeners('focus') // we want to get rid of the focus event added in the FBML above
.addEventListener('focus', this.onfocus.bind(this))
.addEventListener('blur', this.onblur.bind(this))
.addEventListener('keyup', this.onkeyup.bind(this))
.addEventListener('keydown', this.onkeydown.bind(this))
.addEventListener('keypress', this.onkeypress.bind(this));
// Create the dropdown list that contains our suggestions
this.list = document.createElement('div');
this.list.setClassName('th_list')
.setStyle({width: this.obj.getOffsetWidth()-2+'px',
display: 'none'});
this.obj.getParentNode().insertBefore(this.list, this.obj.getNextSibling());
// Various flags
this.focused = true;
this.options = options;
this.selectedindex = -1;
// Styling foo
this.obj.removeClassName('th_placeholder')
.setValue('');
this.update_results();
this.show();
}
typeahead.prototype.max_results = 5;
// Show suggestions when the user focuses the text field
typeahead.prototype.onfocus = function(event) {
this.focused = true;
this.update_results();
this.obj.removeClassName('th_found');
this.show();
}
// ...and hide it when they leave the text field
typeahead.prototype.onblur = function() {
this.focused = true;
this.hide();
}
// Every keypress updates the suggestions
typeahead.prototype.onkeyup = function(event) {
switch (event.keyCode) {
case 27: // escape
this.hide();
this.obj.removeClassName('th_found');
break;
case 0:
case 13: // enter
case 37: // left
case 38: // up
case 39: // right
case 40: // down
break;
default:
this.update_results();
this.show();
this.obj.removeClassName('th_found');
break;
}
}
// We want interactive stuff to happen on keydown to make it feel snappy
typeahead.prototype.onkeydown = function(event) {
switch (event.keyCode) {
case 9: // tab
case 13: // enter
if (this.results[this.selectedindex]) {
this.obj.addClassName('th_found')
.setValue(this.results[this.selectedindex]);
this.hide();
event.preventDefault();
}
break;
case 38: // up
this.select(this.selectedindex - 1);
event.preventDefault();
break;
case 40: // down
this.select(this.selectedindex + 1);
event.preventDefault();
break;
}
}
// Override these events so they don't actually do anything
typeahead.prototype.onkeypress = function(event) {
switch (event.keyCode) {
case 13: // return
case 38: // up
case 40: // down
event.preventDefault();
break;
}
}
// This gets called from our code to select a given index... this is where we would do something interesting like fire off some AJAX or something
typeahead.prototype.select = function(index) {
var children = this.list.getChildNodes();
var found = false;
for (var i = 0; i < children.length; i++) {
if (i == index) {
children[i].addClassName('th_selected');
this.selectedindex = index;
found = true;
} else {
children[i].removeClassName('th_selected');
}
}
if (!found && children[this.selectedindex]) {
children[this.selectedindex].addClassName('th_selected');
}
}
// This is called every keypress to update the suggestions
typeahead.prototype.update_results = function() {
// Search the list of potential results and find ones that match what we have so far
var results = [];
var val = this.obj.getValue().toLowerCase();
this.selectedindex = -1;
for (var i = 0; i < this.options.length; i++) {
var prefix = this.options[i].substring(0, val.length).toLowerCase();
if (prefix == val) {
results.push(this.options[i]);
if (results.length >= this.max_results) {
break;
}
}
}
// Generate a list to display the elements to the user
this.list.setTextValue('');
for (var i = 0; i < results.length; i++) {
this.list.appendChild(
document.createElement('div')
.setClassName('th_suggestion')
.addEventListener('mouseover', function() {
this[0].select(this[1]);
}.bind([this, i]))
.addEventListener('mousedown', function(event) {
this.obj.addClassName('th_found')
.setValue(this.results[this.selectedindex]);
this.hide();
}.bind(this))
.appendChild(document.createElement('em')
.setTextValue(results[i].substring(0, val.length)))
.getParentNode()
.appendChild(document.createElement('span')
.setTextValue(results[i].substring(val.length)))
.getParentNode()
);
}
this.results = results;
}
typeahead.prototype.show = function() {
this.list.setStyle('display', 'block');
}
typeahead.prototype.hide = function() {
this.list.setStyle('display', 'none');
}
//--></script>
<style>
.th_placeholder {
color: #777;
}
.th_found {
background: #e1e9f6;
}
.th_list {
background: transparent;
border: 1px solid #bdc7d8;
border-top: none;
font-size: 11px;
margin-top: -1px;
overflow: hidden;
position: absolute;
text-align: left;
z-index: 102;
}
.th_list .th_suggestion {
background: #fff;
border-top: 1px solid #ddd;
color: #000;
cursor: pointer;
/*filter: alpha(opacity=94);*/
padding: 3px;
opacity: 0.94;
width: 100%;
}
.th_list .th_suggestion em {
background: #d8dfea;
color: black;
font-style: normal;
font-weight: bold;
}
.th_list .th_suggestion small {
color: #808080;
padding-left: 5px;
}
.th_list .th_selected {
background: #3b5998;
color: #fff;
/* filter: alpha(opacity=100);*/
opacity: 1;
}
.th_list .th_selected em {
background: #5670a6;
color: #fff;
}
</style>