Almost every new web site has (or should have) a search box. User skills are improving and people know the basics of using search and use it more often then before. So, your site has a search box (or it’s gonna have one pretty soon).
Search boxes are useful (but boring) by default
The search box is much nicer and much more useful if it has a default value
that can act as many different things: a call to action, an explanation, a teaser of any kind, or just as an outlet for your witty messages. However, once you’ve set a default value, your users are forced into clearing the value manually before actually being able to use the thing. Putting extra work on our users is always a no-no, and as an added bonus we get to have some Javascript fun!
OK, but why?
Because it’s fun! And because I’ve implemented my fair share of those damn search boxes, and implementing the same thing over and over again made me feel stupid. I got tired of it and whipped this thing up so I don’t have to ever think about the issue: I just include the script where it’s needed and voilà!
Another thing that I find annoying is that by default browsers do not select the field contents once the field is focused (in forms, but they do select the contents if you focus a location bar for example) — at least on Windows — so I decided to fix that too.
Onfocus
, onblur
– say what?
Using onfocus
and onblur
event handlers we can add behavior to our search box. Here’s the logic: if the search box receives focus, we’re gonna clear the value of the box (only if the current value is the same as default, or if it’s blank), and when the input looses focus (onblur) we’re gonna set the default value back (only if the user hasn’t changed the value). It might sound confusing, but you can check the search box in the sidebar to see what I’m talking about.
The examples
Some obligatory examples:
The script
The fully commented source is listed below, and a minified version is also available. It’s free to use however you see fit (the MIT License).
It has been tested and confirmed working on all of the following:
- Windows XP / Firefox 2.0.0.11
- Windows XP / Safari 3.0.4 (beta)
- Windows XP / Opera 9.25
- Windows XP / IE 6.0
- Windows Vista / IE 7.0
- Mac OS X 10.5 Leopard / Safari 3.0.4
- Mac OS X 10.5 Leopard / Firefox 2.0.0.11
If anyone has a chance to test on other platforms/browsers and finds a bug or quirk or something, let me know.
Known issues:
- Selecting focused field’s contents using
select()
is acting strangely in Safari (across all the platforms) — looking into it
/**
* Search_Box behavior
* @author zytzagoo
* @version 0.2
*/
function Search_Box(cfg) {
// defaults are always nice
var defaults = { ELEMENT_ID: 'q', DEFAULT_VALUE: 'inherit', FOCUSED_VALUE: '' };
if (cfg) {
// we have a cfg, loop thru the properties
// and make sure something is not missing
// if so, add it from the defaults
for (var name in cfg) {
if (cfg.hasOwnProperty(name)) {
for (var defname in defaults) {
if (defaults.hasOwnProperty(defname)) {
if (!(cfg[defname])) {
cfg[defname] = defaults[defname];
}
}
}
}
}
} else {
cfg = defaults;
}
/**
* return a new object literal with extra
* stuff attached on it
*/
return {
/**
* Checks the element we're working on exists, and
* attaches handlers to it. Usually called after the document
* has loaded or (even better but harder to achieve truly
* cross-browser) when the element referenced by
* Search_Input.ELEMENT_ID is available in the DOM.
*/
init: function () {
var el = document.getElementById(cfg.ELEMENT_ID);
if (el) {
/**
* special case: 'inherit'
* This resets the passed in default value and
* if the element has a previously set value, that
* value is used as the default from now on.
*/
if (cfg.DEFAULT_VALUE === 'inherit') {
cfg.DEFAULT_VALUE = '';
if (el.value !== '') {
cfg.DEFAULT_VALUE = el.value;
}
}
/**
* If a default value is specified, override
* whatever exists in the value attribute of the input in html
*/
/**
* if we have a custom focus handler passed in,
* attach that one too and make sure it is called first
*/
if (cfg.focus) {
Search_Box.attach_handler(el, 'onfocus', cfg.focus);
}
// our own focus handler is always attached
Search_Box.attach_handler(el, 'onfocus', this.focus);
/**
* same as above except this takes care of onblur handlers
*/
if (cfg.blur) {
Search_Box.attach_handler(el, 'onblur', cfg.blur);
}
// our own onblur handler is also always attached
Search_Box.attach_handler(el, 'onblur', this.blur);
/**
* in case the elem has no current value,
* set it to the specified default
*/
if (el.value === '' || (cfg.DEFAULT_VALUE && cfg.DEFAULT_VALUE !== '')) {
el.value = cfg.DEFAULT_VALUE;
}
} else {
throw new Error('Search_Box.init: element (id: "' + cfg.ELEMENT_ID + '") doesn\'t exist');
}
},
/**
* Handles the onfocus event of the element
*/
focus: function (e) {
// delegate, passing in the event object
var t = Search_Box.get_target(e);
// if the target of the event is an input element
if (t.nodeName.toLowerCase() === 'input') {
// if the value of that input is empty or default
if (t.value === cfg.DEFAULT_VALUE || t.value === '') {
// set the value to the specified focused value
t.value = cfg.FOCUSED_VALUE;
/**
* if the now set focused value is not empty
* select the contents of the box
*/
if (t.value !== '') {
t.select();
}
}
}
return true;
},
/**
* Handles the onblur event of the element
*/
blur: function (e) {
// delegate, passing in the event object!
var t = Search_Box.get_target(e);
// if the target of the event is an input element
if (t.nodeName.toLowerCase() === 'input') {
/**
* if the current value of that element is the
* focused value or empty, set the current
* value to the specified default value
*/
if (t.value === cfg.FOCUSED_VALUE || t.value === '') {
t.value = cfg.DEFAULT_VALUE;
}
}
return true;
}
};
}
/**
* Gets the target of an event
*/
Search_Box.get_target = function (x) {
x = x || window.event;
return x.target || x.srcElement;
};
/**
* Attaches event handlers to an existing object.
* If an existing handler is found, it is executed before
* our newly attached handler
*/
Search_Box.attach_handler = function (o, evt, f) {
if (o !== null) {
var existing_handler = o[evt];
if (typeof o[evt] !== 'function') {
/**
* no previous handler found
* TODO: this might need looking into,
* but it seems to work so far...
*/
o[evt] = f;
} else {
/**
* Previous handler found, invoke it,
* while making sure that the 'this' keyword
* inside the handler function refers to the
* input element.
* This enables some cool custom onfocus and
* onblur handlers possible and logical and
* easy to develop and not worry about naming
* stuff...
*/
o[evt] = function (e) {
existing_handler.apply(o, arguments);
f.apply(o, arguments);
};
}
}
}; |
/**
* Search_Box behavior
* @author zytzagoo
* @version 0.2
*/
function Search_Box(cfg) {
// defaults are always nice
var defaults = { ELEMENT_ID: 'q', DEFAULT_VALUE: 'inherit', FOCUSED_VALUE: '' };
if (cfg) {
// we have a cfg, loop thru the properties
// and make sure something is not missing
// if so, add it from the defaults
for (var name in cfg) {
if (cfg.hasOwnProperty(name)) {
for (var defname in defaults) {
if (defaults.hasOwnProperty(defname)) {
if (!(cfg[defname])) {
cfg[defname] = defaults[defname];
}
}
}
}
}
} else {
cfg = defaults;
}
/**
* return a new object literal with extra
* stuff attached on it
*/
return {
/**
* Checks the element we're working on exists, and
* attaches handlers to it. Usually called after the document
* has loaded or (even better but harder to achieve truly
* cross-browser) when the element referenced by
* Search_Input.ELEMENT_ID is available in the DOM.
*/
init: function () {
var el = document.getElementById(cfg.ELEMENT_ID);
if (el) {
/**
* special case: 'inherit'
* This resets the passed in default value and
* if the element has a previously set value, that
* value is used as the default from now on.
*/
if (cfg.DEFAULT_VALUE === 'inherit') {
cfg.DEFAULT_VALUE = '';
if (el.value !== '') {
cfg.DEFAULT_VALUE = el.value;
}
}
/**
* If a default value is specified, override
* whatever exists in the value attribute of the input in html
*/
/**
* if we have a custom focus handler passed in,
* attach that one too and make sure it is called first
*/
if (cfg.focus) {
Search_Box.attach_handler(el, 'onfocus', cfg.focus);
}
// our own focus handler is always attached
Search_Box.attach_handler(el, 'onfocus', this.focus);
/**
* same as above except this takes care of onblur handlers
*/
if (cfg.blur) {
Search_Box.attach_handler(el, 'onblur', cfg.blur);
}
// our own onblur handler is also always attached
Search_Box.attach_handler(el, 'onblur', this.blur);
/**
* in case the elem has no current value,
* set it to the specified default
*/
if (el.value === '' || (cfg.DEFAULT_VALUE && cfg.DEFAULT_VALUE !== '')) {
el.value = cfg.DEFAULT_VALUE;
}
} else {
throw new Error('Search_Box.init: element (id: "' + cfg.ELEMENT_ID + '") doesn\'t exist');
}
},
/**
* Handles the onfocus event of the element
*/
focus: function (e) {
// delegate, passing in the event object
var t = Search_Box.get_target(e);
// if the target of the event is an input element
if (t.nodeName.toLowerCase() === 'input') {
// if the value of that input is empty or default
if (t.value === cfg.DEFAULT_VALUE || t.value === '') {
// set the value to the specified focused value
t.value = cfg.FOCUSED_VALUE;
/**
* if the now set focused value is not empty
* select the contents of the box
*/
if (t.value !== '') {
t.select();
}
}
}
return true;
},
/**
* Handles the onblur event of the element
*/
blur: function (e) {
// delegate, passing in the event object!
var t = Search_Box.get_target(e);
// if the target of the event is an input element
if (t.nodeName.toLowerCase() === 'input') {
/**
* if the current value of that element is the
* focused value or empty, set the current
* value to the specified default value
*/
if (t.value === cfg.FOCUSED_VALUE || t.value === '') {
t.value = cfg.DEFAULT_VALUE;
}
}
return true;
}
};
}
/**
* Gets the target of an event
*/
Search_Box.get_target = function (x) {
x = x || window.event;
return x.target || x.srcElement;
};
/**
* Attaches event handlers to an existing object.
* If an existing handler is found, it is executed before
* our newly attached handler
*/
Search_Box.attach_handler = function (o, evt, f) {
if (o !== null) {
var existing_handler = o[evt];
if (typeof o[evt] !== 'function') {
/**
* no previous handler found
* TODO: this might need looking into,
* but it seems to work so far...
*/
o[evt] = f;
} else {
/**
* Previous handler found, invoke it,
* while making sure that the 'this' keyword
* inside the handler function refers to the
* input element.
* This enables some cool custom onfocus and
* onblur handlers possible and logical and
* easy to develop and not worry about naming
* stuff...
*/
o[evt] = function (e) {
existing_handler.apply(o, arguments);
f.apply(o, arguments);
};
}
}
};