Waldo: Search the JavaScript Object Model in under 1 KB (original) (raw)
Here’s a tiny util that you can save as a bookmarklet and use to crawl the JavaScript object model of any web site. Waldo (thanks to @shinypb for the name!) lets you find and inspect instances by name, type or value, and it can be easily customized to add additional tests. It runs in the console on Chrome, Firefox, Safari and IE>8. It's sourced on github. Feel free to fork it if you want to add more search methods or a spiffy UI.
(Update: Check out this alternate version by @jdalton)
The Basics
Loading the Waldo script will create a global object called find
which invokes a set of utility methods: byName
, byNameContains
, byType
, byValue
, byValueCoerced
and custom
. Each method will crawl the entire runtime JavaScript model from window
down (unless otherwise specified) and report every object that matches the search term argument. There is also an optional options
argument for specifying a) the root object for the search and b) the root of the object path that will display in the search results (the latter option is purely cosmetic).
find.util( searchTerm [, options ] )
examples:
//util = 'byValueCoerced', searchTerm = 'false' find.byValueCoerced(false);
//util = 'byType', searchTerm = Date, rootObject = jQuery, rootPath = 'jQuery' find.byType(Date, {obj: jQuery, path: 'jQuery'});
The Built-in Utilities
Waldo comes with five built-in utilities. Here they are (I’m only showing the mandatory params):
1. find.byName()
Waldo returns all instances whose property name matches the supplied string. For example we might want to look for where Flash is defined in a site…
(moma.org)
…or references to map
in the jQuery runtime…
(twitter.com)
2. find.byNameContains()
This is similar to find.byName
but the search term need only match a substring of the property name:
(dropbox.com)
3. find.byType()
Waldo returns all objects that are an instanceof
the given constructor.
One use case is tracking down all date instances in a site – perhaps to narrow in on a timezone offset bug:
(bbc.com)
Here’s a report of all arrays used in the Huffington Post’s slider utility:
(huffingtonpost.com)
4. find.byValue()
Waldo will perform a strict equality search (===
) against every object in the runtime model. I’ve found this one useful for locating configuration values.
(bbc.com)
5. find.byValueCoerced()
Similar to find.byValue
except this time the equality check allows coercion (==
) – useful for locating falsey values. It’s also convenient when you’re not sure what type you’re searching for – for example the Huffington Post has a “Recent Blog Posts” section with a pagination control showing a maximum value of “4”. I’m looking for supporting code for this control but I’m not sure whether to look for a number
or a string
. No problem:
(huffingtonpost.com)
6. find.custom()
You can use this method to apply any custom function to the search. The function you provide is the one used to match each property of each object found in the runtime model. Here’s the signature…
function(searchTerm, obj, prop) {}
…and here’s an example that finds every truthey value whose property name is ‘_blog’:
(wordpress.com)
Extending Waldo
You can easily add your own utilities to Waldo. Most of the code is generic – you just need to extend the public interface…
window.find={ byName: function(searchTerm, options) {dealWithIt('name', 'string', searchTerm, options);}, byNameContains: function(searchTerm, options) {dealWithIt('nameContains', 'string', searchTerm, options);}, byType: function(searchTerm, options) {dealWithIt('type', 'function', searchTerm, options);}, byValue: function(searchTerm, options) {dealWithIt('value', null, searchTerm, options);}, byValueCoerced: function(searchTerm, options) {dealWithIt('valueCoerced', null, searchTerm, options);}, custom: function(fn, options) {traverse(fn, null, options);} }
…and then define your custom function here…
var tests = { 'name': function(searchTerm, obj, prop) {return searchTerm == prop}, 'nameContains': function(searchTerm, obj, prop) {return prop.indexOf(searchTerm)>-1}, 'type': function(searchTerm, obj, prop) {return obj[prop] instanceof searchTerm}, 'value': function(searchTerm, obj, prop) {return obj[prop] === searchTerm}, 'valueCoerced': function(searchTerm, obj, prop) {return obj[prop] == searchTerm} }
Wrap Up
Here’s the full source code…
(function(){ var traverse = function(util, searchTerm, options) { var options = options || {}; var obj = options.obj || window; var path = options.path || ((obj==window) ? "window" : ""); var props = Object.keys(obj); props.forEach(function(prop) { if ((tests[util] || util)(searchTerm, obj, prop)){ console.log([path, ".", prop].join(""), "->",["(", typeof obj[prop], ")"].join(""), obj[prop]); } if(Object.prototype.toString.call(obj[prop])=="[object Object]" && (obj[prop] != obj) && path.split(".").indexOf(prop) == -1) { traverse(util, searchTerm, {obj: obj[prop], path: [path,prop].join(".")}); } }); }
var dealWithIt = function(util, expected, searchTerm, options) { (!expected || typeof searchTerm == expected) ? traverse(util, searchTerm, options) : console.error([searchTerm, 'must be', expected].join(' ')); }
var tests = { 'name': function(searchTerm, obj, prop) {return searchTerm == prop}, 'nameContains': function(searchTerm, obj, prop) {return prop.indexOf(searchTerm)>-1}, 'type': function(searchTerm, obj, prop) {return obj[prop] instanceof searchTerm}, 'value': function(searchTerm, obj, prop) {return obj[prop] === searchTerm}, 'valueCoerced': function(searchTerm, obj, prop) {return obj[prop] == searchTerm} }
window.find={ byName: function(searchTerm, options) {dealWithIt('name', 'string', searchTerm, options);}, byNameContains: function(searchTerm, options) {dealWithIt('nameContains', 'string', searchTerm, options);}, byType: function(searchTerm, options) {dealWithIt('type', 'function', searchTerm, options);}, byValue: function(searchTerm, options) {dealWithIt('value', null, searchTerm, options);}, byValueCoerced: function(searchTerm, options) {dealWithIt('valueCoerced', null, searchTerm, options);}, custom: function(fn, options) {traverse(fn, null, options);} } })();
…and here’s the minified source should you want to create a bookmarklet
javascript:(function(){var c=function(d,e,f){var f=f||{};var i=f.obj||window;var h=f.path||((i==window)?"window":"");var g=Object.keys(i);g.forEach(function(j){if((b[d]||d)(e,i,j)){console.log([h,".",j].join(""),"->",["(",typeof i[j],")"].join(""),i[j])}if(Object.prototype.toString.call(i[j])=="[object Object]"&&(i[j]!=i)&&h.split(".").indexOf(j)==-1){c(d,e,{obj:i[j],path:[h,j].join(".")})}})};var a=function(d,g,e,f){(!g||typeof e==g)?c(d,e,f):console.error([e,"must be",g].join(" "))};var b={name:function(d,e,f){return d==f},nameContains:function(d,e,f){return f.indexOf(d)>-1},type:function(d,e,f){return e[f] instanceof d},value:function(d,e,f){return e[f]===d},valueCoerced:function(d,e,f){return e[f]==d}};window.find={byName:function(d,e){a("name","string",d,e)},byNameContains:function(d,e){a("nameContains","string",d,e)},byType:function(d,e){a("type","function",d,e)},byValue:function(d,e){a("value",null,d,e)},byValueCoerced:function(d,e){a("valueCoerced",null,d,e)},custom:function(e,d){c(e,null,d)}}})();
Both sources are also available on github. I hope you have fun using Waldo and look forwarding to seeing how folks are able to fork it with extra coolness!