Skip to content

Instantly share code, notes, and snippets.

@graynorton
Created November 10, 2008 22:11
Show Gist options
  • Save graynorton/23656 to your computer and use it in GitHub Desktop.
Save graynorton/23656 to your computer and use it in GitHub Desktop.
// A dependency, from Douglas Crockford's "JavaScript: The Good Parts"
if (typeof Object.create !== 'function') {
Object.create = function (o) {
var F = function () {};
F.prototype = o;
return new F();
};
}
// freebaseNounType
///////////////////////////////////////////////////
// Builds Ubiquity nouns based on Freebase types
function freebaseNounType( name, typeFilter ) {
var that, onPause;
onPause = new Waiter( 400 );
that = {
_name: name,
suggest: function suggest( text, html, callback ) {
onPause.please( function() { freebaseNounType.manager.getSuggestions( text, callback, typeFilter ) } );
return [];
}
}
return that;
}
freebaseNounType.manager = freebaseNounTypeManager();
// freebaseNounTypeManager
//////////////////////////////////////////////////////////////////
// Coordinates simultaneous requests from multiple Freebase nouns,
// allowing them to be relevance-sorted together and returned in
// predictable order
function freebaseNounTypeManager() {
var that, maxSuggestions, openRequests;
that = {};
maxSuggestions = 5;
openRequests = {};
function request( text ) {
var that, subRequests, subRequestsClosed, afterLastSubRequest, suggestions, closed, suggested;
that = {};
subRequests = 0;
subRequestsClosed = 0;
afterLastSubRequest = new Waiter( 50 );
suggestions = [];
closed = false;
suggested = false;
function queueSuggestion( result, callback ) {
var suggestion = function suggestion() { callback( CmdUtils.makeSugg( result.name, result.name, result ) ); };
suggestion.score = result[ "relevance:score" ];
suggestions.push( suggestion );
}
function makeSuggestions() {
suggestions = suggestions.sort( function( a, b ) {
var scoreA = parseFloat( a.score );
var scoreB = parseFloat( b.score );
if ( scoreA > scoreB ) {
return -1;
}
if ( scoreA < scoreB ) {
return 1;
}
return 0;
} );
for ( var i = 0; i < suggestions.length && i < maxSuggestions; i++ ) {
suggestions[ i ]();
}
suggested = true;
}
function close() {
delete openRequests[ text ];
closed = true;
if ( !suggested && openSubRequests() === 0 ) {
makeSuggestions();
}
}
function openSubRequest() {
subRequests++;
afterLastSubRequest.please( close );
}
function closeSubRequest() {
subRequestsClosed ++;
if ( closed && openSubRequests() === 0 ) {
makeSuggestions();
}
}
function openSubRequests() {
return subRequests - subRequestsClosed;
}
that.queueSuggestion = queueSuggestion;
that.openSubRequest = openSubRequest;
that.closeSubRequest = closeSubRequest;
openRequests[ text ] = that;
return that;
}
function getSuggestions( text, callback, typeFilter ) {
var req = openRequests[ text ] || request( text );
req.openSubRequest();
var params = {
prefix: text,
limit: maxSuggestions,
success: function success( freebaseResponse ) {
var results = freebaseResponse.result;
for ( var idx = 0; idx < results.length; idx++ ) {
var result = results[idx];
req.queueSuggestion( result, callback );
}
req.closeSubRequest();
}
};
if ( typeFilter && typeFilter.length ) {
params.typeFilter = typeFilter;
}
Freebase.search( params );
}
that.getSuggestions = getSuggestions;
return that;
}
// Freebase
///////////////////////////////////////////////////////////////////
// Reusable Freebase functionality, wrapping Freebase API calls
var Freebase = {
_decodeWin1252: function _decodeWin1252( str ) {
return str.replace( /\$([0-9A-F]{4})/g,
function( whole, val ) {
return String.fromCharCode( parseInt( val, 16 ) );
} );
},
wrapQuery: function wrapQuery( query, queryLabel ) {
if ( !queryLabel ) {
queryLabel = "response";
}
var inner = { "query" : query };
var outer = {};
outer[ queryLabel ] = inner;
return outer;
},
// id*, maxwidth, maxheight, mode, onfail
imageThumbUrl: function imageThumbUrl( params ) {
if ( params.id === undefined ) { return ""; }
var url = "http://www.freebase.com/api/trans/image_thumb/" + params.id + "?";
for ( var param in params ) {
if ( param === "id" ) { continue; }
url += param + "=" + encodeURIComponent( params[ param ] ) + "&";
}
return url;
},
// id*, maxlength, break_paragraphs
blurb: new UbiquityAjaxFn( {
name: "Freebase blurb",
url: "http://www.freebase.com/api/trans/blurb",
args: { maxlength: 0, break_paragraphs: 0 },
dataType: "text",
beforeBuild: function() {
this.url += this.id;
delete this.id;
}
} ),
// (query|prefix)*, type, limit
search: new UbiquityAjaxFn( {
name: "Freebase search",
url: "http://www.freebase.com/api/service/search",
args: { query: 1, prefix: 1, typeFilter: 0, limit: 0 },
beforeBuild: function beforeBuild() {
if ( this.data.typeFilter !== undefined ) {
this.data.type = this.data.typeFilter;
delete this.data.typeFilter;
}
}
} ),
// query*
mqlRead: new UbiquityAjaxFn( {
name: "Freebase mqlRead",
type: "POST",
url: "http://www.freebase.com/api/service/mqlread",
beforeBuild: function() {
this.data = "queries=" + Utils.encodeJson( Freebase.wrapQuery( this.query ) );
delete this.query;
},
dataFilter: function( data, type ) {
return Freebase._decodeWin1252( data );
}
} )
}
// UbiquityAjaxFn
//////////////////////////////////////////////////////////////////////////////
// Builds Ajax functions for use in Ubiquity nouns and previews: sets some
// defaults, handles caching and calls CmdUtils.previewAjax when applicable
function UbiquityAjaxFn( defParams ) {
var defaults = {
name: "Ajax",
args: {},
error: function error( XMLHttpRequest, textStatus, errorThrown ) {
CmdUtils.log( p.name + " error: " + textStatus );
},
success: function success( data, textStatus ) {
var responseDisplay = ( typeof data === "Object" ) ? Utils.encodeJson( data ) : data;
CmdUtils.log( p.name + " response: " + responseDisplay );
},
dataType: "json",
cache: true
}
function _processArgs() {
if ( this.data === undefined ) {
this.data = {};
}
for ( var arg in this.args ) {
if ( this[ arg ] !== undefined ) {
this.data[ arg ] = this[ arg ];
delete this[ arg ];
}
}
}
var p = Object.create( defaults );
for ( var key in defParams ) {
p[ key ] = defParams[ key ];
}
var cache = ( p.cache ) ? new Cache() : null;
var fn = function( runParams ) {
var r = Object.create( p );
var cacheSignature = {};
for ( var key in runParams ) {
r[ key ] = runParams[ key ];
// Use runtime params as keys for the cache, but omit functions and DOM elements
if ( typeof runParams[ key ] !== "function" && runParams[ key ].nodeType === undefined ) {
cacheSignature[ key ] = runParams[ key ];
}
}
_processArgs.call( r );
if ( r.beforeBuild !== undefined ) {
r.beforeBuild.call( r );
}
if ( cache && r.cache ) {
var cachedResponse = cache.read( cacheSignature );
if ( cachedResponse ) {
return r.success( cachedResponse );
}
var origSuccess = r.success;
r.success = function success( data, textStatus ) {
cache.write( cacheSignature, data );
origSuccess( data, textStatus );
}
}
if ( r.container === undefined ) {
jQuery.ajax( r );
}
else {
CmdUtils.previewAjax( r.container, r );
}
}
return fn;
}
// Cache
/////////////////////////////////////////
// Simple cache, used by UbiquityAjaxFn
function Cache() {
var _cache = {};
function _key( request ) {
if ( typeof request === "object" ) {
var key = Utils.encodeJson( request );
return key;
}
return query;
}
this.write = function write( request, response ) {
_cache[ _key( request ) ] = response;
}
this.read = function read( request ) {
var response = _cache[ _key( request ) ];
return response;
}
}
// Waiter
/////////////////////////////////////////////////////////////
// Used by Freebase nouns to wait for a pause in typing so
// that we don't pepper Freebase with unnecessary requests,
// and to wait for suggestions to stop coming back before
// returning them from the queue
function Waiter( delay ) {
if ( !delay ) { delay = 300; }
var timer = null;
this.please = function please( fn ) {
if (timer) {
Utils.clearTimeout( timer );
}
timer = Utils.setTimeout ( function() {
timer = null;
fn();
}, delay );
}
}
//////////////////////
// Currently Unused //
//////////////////////
/*
// Was doing my own Freebase noun search before discovering Freebase's search service.
// With a bit more work, might still be useful, especially for generating
// nouns that need to differentiate based on something more than just name
// and type.
_get: function _get( callback, typeFilter, customParams ) {
var params = {
name: null,
id: null,
type: [ {
attribution : "/user/metaweb",
id: null,
name: null
} ],
key: [ {
// Was using "/wikipedia/en_id", but that required an extra Ajax request
// to get the page name, so trying "/wikipedia/en"[0] on the (unverified)
// assumption that the first Wikipedia page in each Freebase
// record is the canonical page
namespace: "/wikipedia/en",
value: null,
limit: 1
} ],
limit: this._maxSuggestions
};
if ( typeFilter !== undefined ) {
for ( var idx = 0; idx++; idx < typeFilter.length ) {
params[ "f" + idx + ":type" ] = typeFilter[ idx ];
}
}
for ( var key in customParams ) {
params[ key ] = customParams[ key ];
}
var query = [ params ];
Freebase.mqlRead( {
query: query,
success: callback
} );
},
getTopExactMatch: function getTopExactMatch( text, callback, typeFilter ) {
var params = { "name~=": "^" + text.replace(/ /g, "\\ ") + "$", limit: 1 };
this._get( callback, typeFilter, params );
},
getExactMatches: function getExactMatches( text, callback, typeFilter ) {
var params = { name: text };
this._get( callback, typeFilter, params );
},
getCompletions: function getCompletions( text, callback, typeFilter ) {
var params = { "name~=": "^" + text + "*" };
this._get( callback, typeFilter, params );
},
getSimilar: function getSimilar( text, callback, typeFilter ) {
var params = { "name~=": "*" + text + "*" };
this._get( callback, typeFilter, params );
},
getSuggestions: function getSuggestions( text, callback, typeFilter ) {
var that = this;
var methods = [ this.getTopExactMatch, this.getCompletions, this.getSimilar ];
var currentMethod = 0;
var suggested = {}, numSuggested = 0;
tryMethod();
function tryMethod() {
methods[ currentMethod ].call( that, text, processResults, typeFilter);
function processResults( freebaseResponse ) {
var results = freebaseResponse.response.result;
for ( var idx in results ) {
var result = results[ idx ];
if ( suggested[ result.id ] === undefined ) {
callback( CmdUtils.makeSugg( result.name, result.name, result ) );
suggested[ result.id ] = true;
if ( ++numSuggested === that._maxSuggestions) return;
}
}
if ( numSuggested < that._maxSuggestions && ++currentMethod < methods.length ) tryMethod();
}
}
}
// Was getting description and image from Wikipedia OpenSearch before discovering Freebase search
function extractDescAndImage( results ) {
const maxDim = 150;
if (!results.Section.Item) {
About.meta.content.description = "";
}
else {
var item = ( results.Section.Item.Description ) ? results.Section.Item : results.Section.Item[0];
About.meta.content.description = item.Description;
if ( item.Image ) {
var aspectRatio = item.Image.width / item.Image.height;
var targetWidth, targetHeight;
if ( aspectRatio > 1 ) {
targetWidth = maxDim;
targetHeight = Math.round( targetWidth / aspectRatio );
}
else {
targetHeight = maxDim;
targetWidth = Math.round( targetHeight * aspectRatio );
}
item.Image.source = item.Image.source.replace( /[0-9]+px/, targetWidth + "px" );
About.meta.content.image = {
src: item.Image.source,
height: targetHeight,
width: targetWidth
}
}
}
About.meta.content.status = null;
render();
}
// Shell for some reusable Wikipedia functionality, just OpenSearch so far.
var Wikipedia = {
_openSearchCache: new Cache(),
openSearch: new UbiquityAjaxFn( {
name: "Wikipedia OpenSearch",
url: "http://en.wikipedia.org/w/api.php",
args: { search: 1 },
dataType: "xml",
data: {
action: "opensearch",
format: "xml" // Asking for XML because it contains info not included in the JSON version (image, abstract, etc.)
},
beforeBuild: function beforeBuild() {
var origSuccess = this.success;
this.success = function success( data, textStatus ) {
var json = jQuery.xml2json( data );
origSuccess( json, textStatus );
}
}
} )
}
// Dependency for Wikipedia OpenSearch implementation above
jQuery.get( "http://www.fyneworks.com/jquery/xml-to-json/jquery.xml2json.js",
null,
function( script ) {
eval( script ); // using eval because jQuery.getScript doesn't seem to work in Ubiquity Command sandbox
},
"text" );
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment