2013-01-05 21:24:52 +01:00
/ *
* Copyright 2012 , Google Inc .
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are
* met :
*
* * Redistributions of source code must retain the above copyright
* notice , this list of conditions and the following disclaimer .
* * Redistributions in binary form must reproduce the above
* copyright notice , this list of conditions and the following disclaimer
* in the documentation and / or other materials provided with the
* distribution .
* * Neither the name of Google Inc . nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT
* LIMITED TO , THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL ,
* SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT
* LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE ,
* DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*
* @ author Dae Park ( daepark @ google . com )
* /
( function ( $ , undefined ) {
if ( ! ( "console" in window ) ) {
var c = window . console = { } ;
c . log = c . warn = c . error = c . debug = function ( ) { } ;
}
/ * *
* jQuery UI provides a way to be notified when an element is removed from the DOM .
* suggest would like to use this facility to properly teardown it ' s elements from the DOM ( suggest list , flyout , etc . ) .
* The following logic tries to determine if "remove" event is already present , else
* tries to mimic what jQuery UI does ( as of 1.8 . 5 ) by adding a hook to $ . cleanData or $ . fn . remove .
* /
$ ( function ( ) {
var div = $ ( "<div>" ) ;
$ ( document . body ) . append ( div ) ;
var t = setTimeout ( function ( ) {
// copied from jquery-ui
// for remove event
if ( $ . cleanData ) {
var _cleanData = $ . cleanData ;
$ . cleanData = function ( elems ) {
for ( var i = 0 , elem ; ( elem = elems [ i ] ) != null ; i ++ ) {
$ ( elem ) . triggerHandler ( "remove" ) ;
}
_cleanData ( elems ) ;
} ;
}
else {
var _remove = $ . fn . remove ;
$ . fn . remove = function ( selector , keepData ) {
return this . each ( function ( ) {
if ( ! keepData ) {
if ( ! selector || $ . filter ( selector , [ this ] ) . length ) {
$ ( "*" , this ) . add ( [ this ] ) . each ( function ( ) {
$ ( this ) . triggerHandler ( "remove" ) ;
} ) ;
}
}
return _remove . call ( $ ( this ) , selector , keepData ) ;
} ) ;
} ;
}
} , 1 ) ;
div . bind ( "remove" , function ( ) {
clearTimeout ( t ) ;
} ) ;
div . remove ( ) ;
} ) ;
/ * *
* These are the search parameters that are transparently passed
* to the search service as specified by service _url + service _path
* /
var SEARCH _PARAMS = {
key : 1 , filter : 1 , spell : 1 , exact : 1 ,
lang : 1 , scoring : 1 , prefixed : 1 , stemmed : 1 , format : 1 , mql _output : 1 ,
2013-03-02 00:57:11 +01:00
output : 1 , type : 1
2013-01-05 21:24:52 +01:00
} ;
$ . suggest = function ( name , prototype ) {
$ . fn [ name ] = function ( options ) {
if ( ! this . length ) {
console . warn ( 'Suggest: invoked on empty element set' ) ;
}
return this
. each ( function ( ) {
if ( this . nodeName ) {
if ( this . nodeName . toUpperCase ( ) === 'INPUT' ) {
if ( this . type && this . type . toUpperCase ( ) !== 'TEXT' ) {
console . warn ( 'Suggest: unsupported INPUT type: ' + this . type ) ;
}
}
else {
console . warn ( 'Suggest: unsupported DOM element: ' + this . nodeName ) ;
}
}
var instance = $ . data ( this , name ) ;
if ( instance ) {
instance . _destroy ( ) ;
}
$ . data ( this , name , new $ . suggest [ name ] ( this , options ) ) . _init ( ) ;
} ) ;
} ;
$ . suggest [ name ] = function ( input , options ) {
var self = this ,
o = this . options = $ . extend ( true , { } ,
$ . suggest . defaults ,
$ . suggest [ name ] . defaults ,
options ) ,
pfx = o . css _prefix = o . css _prefix || "" ,
css = o . css ;
this . name = name ;
$ . each ( css , function ( k , v ) {
css [ k ] = pfx + css [ k ] ;
} ) ;
// suggest parameters
o . ac _param = { } ;
$ . each ( SEARCH _PARAMS , function ( k ) {
var v = o [ k ] ;
if ( v === null || v === "" ) {
return ;
}
o . ac _param [ k ] = v ;
} ) ;
// flyout service lang is the first specified lang
o . flyout _lang = null ;
if ( o . ac _param . lang ) {
var lang = o . ac _param . lang ;
if ( $ . type ( lang ) === "string" ) {
lang = lang . split ( "," ) ;
}
if ( $ . isArray ( lang ) && lang . length ) {
lang = $ . trim ( lang [ 0 ] ) ;
if ( lang ) {
o . flyout _lang = lang ;
}
}
}
// status texts
this . _status = {
START : "" ,
LOADING : "" ,
SELECT : "" ,
ERROR : ""
} ;
if ( o . status && o . status instanceof Array && o . status . length >= 3 ) {
this . _status . START = o . status [ 0 ] || "" ;
this . _status . LOADING = o . status [ 1 ] || "" ;
this . _status . SELECT = o . status [ 2 ] || "" ;
if ( o . status . length === 4 ) {
this . _status . ERROR = o . status [ 3 ] || "" ;
}
}
// create the container for the drop down list
var s = this . status = $ ( '<div style="display:none;">' ) . addClass ( css . status ) ,
l = this . list = $ ( "<ul>" ) . addClass ( css . list ) ,
p = this . pane = $ ( '<div style="display:none;" class="fbs-reset">' ) . addClass ( css . pane ) ;
p . append ( s ) . append ( l ) ;
if ( o . parent ) {
$ ( o . parent ) . append ( p ) ;
}
else {
p . css ( "position" , "absolute" ) ;
if ( o . zIndex ) {
p . css ( "z-index" , o . zIndex ) ;
}
$ ( document . body ) . append ( p ) ;
}
p . bind ( "mousedown" , function ( e ) {
//console.log("pane mousedown");
self . input . data ( "dont_hide" , true ) ;
e . stopPropagation ( ) ;
} )
. bind ( "mouseup" , function ( e ) {
//console.log("pane mouseup");
if ( self . input . data ( "dont_hide" ) ) {
self . input . focus ( ) ;
}
self . input . removeData ( "dont_hide" ) ;
e . stopPropagation ( ) ;
} )
. bind ( "click" , function ( e ) {
//console.log("pane click");
e . stopPropagation ( ) ;
var s = self . get _selected ( ) ;
if ( s ) {
self . onselect ( s , true ) ;
self . hide _all ( ) ;
}
} ) ;
var hoverover = function ( e ) {
self . hoverover _list ( e ) ;
} ;
var hoverout = function ( e ) {
self . hoverout _list ( e ) ;
} ;
l . hover ( hoverover , hoverout ) ;
//console.log(this.pane, this.list);
this . input = $ ( input )
. attr ( "autocomplete" , "off" )
. unbind ( ".suggest" )
. bind ( "remove.suggest" , function ( e ) {
self . _destroy ( ) ;
} )
. bind ( "keydown.suggest" , function ( e ) {
self . keydown ( e ) ;
} )
. bind ( "keypress.suggest" , function ( e ) {
self . keypress ( e ) ;
} )
. bind ( "keyup.suggest" , function ( e ) {
self . keyup ( e ) ;
} )
. bind ( "blur.suggest" , function ( e ) {
self . blur ( e ) ;
} )
. bind ( "textchange.suggest" , function ( e ) {
self . textchange ( ) ;
} )
. bind ( "focus.suggest" , function ( e ) {
self . focus ( e ) ;
} )
. bind ( $ . browser . msie ? "paste.suggest" : "input.suggest" , function ( e ) {
clearTimeout ( self . paste _timeout ) ;
self . paste _timeout = setTimeout ( function ( ) {
self . textchange ( ) ;
} , 0 ) ;
} ) ;
// resize handler
this . onresize = function ( e ) {
self . invalidate _position ( ) ;
if ( p . is ( ":visible" ) ) {
self . position ( ) ;
if ( o . flyout && self . flyoutpane && self . flyoutpane . is ( ":visible" ) ) {
var s = self . get _selected ( ) ;
if ( s ) {
self . flyout _position ( s ) ;
}
}
}
} ;
$ ( window )
. bind ( "resize.suggest" , this . onresize )
. bind ( "scroll.suggest" , this . onresize ) ;
} ;
$ . suggest [ name ] . prototype = $ . extend ( { } , $ . suggest . prototype , prototype ) ;
} ;
// base suggest prototype
$ . suggest . prototype = {
_init : function ( ) { } ,
_destroy : function ( ) {
this . pane . remove ( ) ;
this . list . remove ( ) ;
this . input . unbind ( ".suggest" ) ;
$ ( window )
. unbind ( "resize.suggest" , this . onresize )
. unbind ( "scroll.suggest" , this . onresize ) ;
this . input . removeData ( "data.suggest" ) ;
} ,
invalidate _position : function ( ) {
self . _position = null ;
} ,
status _start : function ( ) {
this . hide _all ( ) ;
this . status . siblings ( ) . hide ( ) ;
if ( this . _status . START ) {
this . status . text ( this . _status . START ) . show ( ) ;
if ( ! this . pane . is ( ":visible" ) ) {
this . position ( ) ;
this . pane _show ( ) ;
}
}
if ( this . _status . LOADING ) {
this . status . removeClass ( "loading" ) ;
}
} ,
status _loading : function ( ) {
this . status . siblings ( ) . show ( ) ;
if ( this . _status . LOADING ) {
this . status . addClass ( "loading" ) . text ( this . _status . LOADING ) . show ( ) ;
if ( ! this . pane . is ( ":visible" ) ) {
this . position ( ) ;
this . pane _show ( ) ;
}
}
else {
this . status . hide ( ) ;
}
} ,
status _select : function ( ) {
this . status . siblings ( ) . show ( ) ;
if ( this . _status . SELECT ) {
this . status . text ( this . _status . SELECT ) . show ( ) ;
}
else {
this . status . hide ( ) ;
}
if ( this . _status . LOADING ) {
this . status . removeClass ( "loading" ) ;
}
} ,
status _error : function ( ) {
this . status . siblings ( ) . show ( ) ;
if ( this . _status . ERROR ) {
this . status . text ( this . _status . ERROR ) . show ( ) ;
}
else {
this . status . hide ( ) ;
}
if ( this . _status . LOADING ) {
this . status . removeClass ( "loading" ) ;
}
} ,
focus : function ( e ) {
//console.log("focus", this.input.val() === "");
var o = this . options ,
v = this . input . val ( ) ;
if ( v === "" ) {
this . status _start ( ) ;
}
else {
this . focus _hook ( e ) ;
}
} ,
// override to be notified on focus and input has a value
focus _hook : function ( e ) {
//console.log("focus_hook", this.input.data("data.suggest"));
if ( ! this . input . data ( "data.suggest" ) &&
! this . pane . is ( ":visible" ) &&
$ ( "." + this . options . css . item , this . list ) . length ) {
this . position ( ) ;
this . pane _show ( ) ;
}
} ,
keydown : function ( e ) {
var key = e . keyCode ;
if ( key === 9 ) { // tab
this . tab ( e ) ;
}
else if ( key === 38 || key === 40 ) { // up/down
if ( ! e . shiftKey ) {
// prevents cursor/caret from moving (in Safari)
e . preventDefault ( ) ;
}
}
} ,
keypress : function ( e ) {
var key = e . keyCode ;
if ( key === 38 || key === 40 ) { // up/down
if ( ! e . shiftKey ) {
// prevents cursor/caret from moving
e . preventDefault ( ) ;
}
}
else if ( key === 13 ) { // enter
this . enter ( e ) ;
}
} ,
keyup : function ( e ) {
var key = e . keyCode ;
//console.log("keyup", key);
if ( key === 38 ) { // up
e . preventDefault ( ) ;
this . up ( e ) ;
}
else if ( key === 40 ) { // down
e . preventDefault ( ) ;
this . down ( e ) ;
}
else if ( e . ctrlKey && key === 77 ) {
$ ( ".fbs-more-link" , this . pane ) . click ( ) ;
}
else if ( $ . suggest . is _char ( e ) ) {
//this.textchange();
clearTimeout ( this . keypress . timeout ) ;
var self = this ;
this . keypress . timeout = setTimeout ( function ( ) {
self . textchange ( ) ;
} , 0 ) ;
}
else if ( key === 27 ) {
// escape - WebKit doesn't fire keypress for escape
this . escape ( e ) ;
}
return true ;
} ,
blur : function ( e ) {
//console.log("blur", "dont_hide", this.input.data("dont_hide"),
// "data.suggest", this.input.data("data.suggest"));
if ( this . input . data ( "dont_hide" ) ) {
return ;
}
var data = this . input . data ( "data.suggest" ) ;
this . hide _all ( ) ;
} ,
tab : function ( e ) {
if ( e . shiftKey || e . metaKey || e . ctrlKey ) {
return ;
}
var o = this . options ,
visible = this . pane . is ( ":visible" ) &&
$ ( "." + o . css . item , this . list ) . length ,
s = this . get _selected ( ) ;
//console.log("tab", visible, s);
if ( visible && s ) {
this . onselect ( s ) ;
this . hide _all ( ) ;
}
} ,
enter : function ( e ) {
var o = this . options ,
visible = this . pane . is ( ":visible" ) ;
//console.log("enter", visible);
if ( visible ) {
if ( e . shiftKey ) {
this . shift _enter ( e ) ;
e . preventDefault ( ) ;
}
else if ( $ ( "." + o . css . item , this . list ) . length ) {
var s = this . get _selected ( ) ;
if ( s ) {
this . onselect ( s ) ;
this . hide _all ( ) ;
e . preventDefault ( ) ;
}
else {
var data = this . input . data ( "data.suggest" ) ;
if ( $ ( "." + this . options . css . item + ":visible" , this . list ) . length ) {
this . updown ( false ) ;
e . preventDefault ( ) ;
}
}
}
//console.log("enter preventDefault");
}
} ,
shift _enter : function ( e ) { } ,
escape : function ( e ) {
this . hide _all ( ) ;
} ,
up : function ( e ) {
//console.log("up");
this . updown ( true , e . ctrlKey || e . shiftKey ) ;
} ,
down : function ( e ) {
//console.log("up");
this . updown ( false , null , e . ctrlKey || e . shiftKey ) ;
} ,
updown : function ( goup , gofirst , golast ) {
//console.log("updown", goup, gofirst, golast);
var o = this . options ,
css = o . css ,
p = this . pane ,
l = this . list ;
if ( ! p . is ( ":visible" ) ) {
if ( ! goup ) {
this . textchange ( ) ;
}
return ;
}
var li = $ ( "." + css . item + ":visible" , l ) ;
if ( ! li . length ) {
return ;
}
var first = $ ( li [ 0 ] ) ,
last = $ ( li [ li . length - 1 ] ) ,
cur = this . get _selected ( ) || [ ] ;
clearTimeout ( this . ignore _mouseover . timeout ) ;
this . _ignore _mouseover = false ;
if ( goup ) { //up
if ( gofirst ) {
this . _goto ( first ) ;
}
else if ( ! cur . length ) {
this . _goto ( last ) ;
}
else if ( cur [ 0 ] == first [ 0 ] ) {
first . removeClass ( css . selected ) ;
this . input . val ( this . input . data ( "original.suggest" ) ) ;
this . hoverout _list ( ) ;
}
else {
var prev = cur . prevAll ( "." + css . item + ":visible:first" ) ;
this . _goto ( prev ) ;
}
}
else { //down
if ( golast ) {
this . _goto ( last ) ;
}
else if ( ! cur . length ) {
this . _goto ( first ) ;
}
else if ( cur [ 0 ] == last [ 0 ] ) {
last . removeClass ( css . selected ) ;
this . input . val ( this . input . data ( "original.suggest" ) ) ;
this . hoverout _list ( ) ;
}
else {
var next = cur . nextAll ( "." + css . item + ":visible:first" ) ;
this . _goto ( next ) ;
}
}
} ,
_goto : function ( li ) {
li . trigger ( "mouseover.suggest" ) ;
var d = li . data ( "data.suggest" ) ;
this . input . val ( d ? d . name : this . input . data ( "original.suggest" ) ) ;
this . scroll _to ( li ) ;
} ,
scroll _to : function ( item ) {
var l = this . list ,
scrollTop = l . scrollTop ( ) ,
scrollBottom = scrollTop + l . innerHeight ( ) ,
item _height = item . outerHeight ( ) ,
offsetTop = item . prevAll ( ) . length * item _height ,
offsetBottom = offsetTop + item _height ;
if ( offsetTop < scrollTop ) {
this . ignore _mouseover ( ) ;
l . scrollTop ( offsetTop ) ;
}
else if ( offsetBottom > scrollBottom ) {
this . ignore _mouseover ( ) ;
l . scrollTop ( scrollTop + offsetBottom - scrollBottom ) ;
}
} ,
textchange : function ( ) {
this . input . removeData ( "data.suggest" ) ;
this . input . trigger ( "fb-textchange" , this ) ;
var v = this . input . val ( ) ;
if ( v === "" ) {
this . status _start ( ) ;
return ;
}
else {
this . status _loading ( ) ;
}
this . request ( v ) ;
} ,
request : function ( ) { } ,
response : function ( data ) {
if ( ! data ) {
return ;
}
if ( "cost" in data ) {
this . trackEvent ( this . name , "response" , "cost" , data . cost ) ;
}
if ( ! this . check _response ( data ) ) {
return ;
}
var result = [ ] ;
if ( $ . isArray ( data ) ) {
result = data ;
}
else if ( "result" in data ) {
result = data . result ;
}
var args = $ . map ( arguments , function ( a ) {
return a ;
} ) ;
this . response _hook . apply ( this , args ) ;
var first = null ,
self = this ,
o = this . options ;
$ . each ( result , function ( i , n ) {
if ( ! n . id && n . mid ) {
// For compatitibility reasons, store the mid as id
n . id = n . mid ;
}
var li = self . create _item ( n , data )
. bind ( "mouseover.suggest" , function ( e ) {
self . mouseover _item ( e ) ;
} ) ;
li . data ( "data.suggest" , n ) ;
self . list . append ( li ) ;
if ( i === 0 ) {
first = li ;
}
} ) ;
this . input . data ( "original.suggest" , this . input . val ( ) ) ;
if ( $ ( "." + o . css . item , this . list ) . length === 0 && o . nomatch ) {
var $nomatch = $ ( '<li class="fbs-nomatch">' ) ;
if ( typeof o . nomatch === "string" ) {
$nomatch . text ( o . nomatch ) ;
}
else {
if ( o . nomatch . title ) {
$nomatch . append ( $ ( '<em class="fbs-nomatch-text">' ) . text ( o . nomatch . title ) ) ;
}
if ( o . nomatch . heading ) {
$nomatch . append ( $ ( '<h3>' ) . text ( o . nomatch . heading ) ) ;
}
var tips = o . nomatch . tips ;
if ( tips && tips . length ) {
var $tips = $ ( '<ul class="fbs-search-tips">' ) ;
$ . each ( tips , function ( i , tip ) {
$tips . append ( $ ( "<li>" ) . text ( tip ) ) ;
} ) ;
$nomatch . append ( $tips ) ;
}
}
$nomatch . bind ( "click.suggest" , function ( e ) {
e . stopPropagation ( ) ;
} ) ;
this . list . append ( $nomatch ) ;
}
args . push ( first ) ;
this . show _hook . apply ( this , args ) ;
this . position ( ) ;
this . pane _show ( ) ;
} ,
pane _show : function ( ) {
var show = false ;
if ( $ ( "> li" , this . list ) . length ) {
show = true ;
}
if ( ! show ) {
this . pane . children ( ":not(." + this . options . css . list + ")" )
. each ( function ( ) {
if ( $ ( this ) . css ( "display" ) != "none" ) {
show = true ;
return false ;
}
} ) ;
}
if ( show ) {
if ( this . options . animate ) {
var self = this ;
this . pane . slideDown ( "fast" , function ( ) {
self . input . trigger ( "fb-pane-show" , self ) ;
} ) ;
}
else {
this . pane . show ( ) ;
this . input . trigger ( "fb-pane-show" , this ) ;
}
}
else {
this . pane . hide ( ) ;
this . input . trigger ( "fb-pane-hide" , this ) ;
}
} ,
create _item : function ( data , response _data ) {
var css = this . options . css ;
var li = $ ( "<li>" ) . addClass ( css . item ) ;
var label = $ ( "<label>" ) . text ( data . name ) ;
li . append ( $ ( "<div>" ) . addClass ( css . item _name ) . append ( label ) ) ;
return li ;
} ,
mouseover _item : function ( e ) {
if ( this . _ignore _mouseover ) {
return ;
}
var target = e . target ;
if ( target . nodeName . toLowerCase ( ) !== "li" ) {
target = $ ( target ) . parents ( "li:first" ) ;
}
var li = $ ( target ) ,
css = this . options . css ,
l = this . list ;
$ ( "." + css . item , l )
. each ( function ( ) {
if ( this !== li [ 0 ] ) {
$ ( this ) . removeClass ( css . selected ) ;
}
} ) ;
if ( ! li . hasClass ( css . selected ) ) {
li . addClass ( css . selected ) ;
this . mouseover _item _hook ( li ) ;
}
} ,
mouseover _item _hook : function ( $li ) { } ,
hoverover _list : function ( e ) { } ,
hoverout _list : function ( e ) { } ,
check _response : function ( response _data ) {
return true ;
} ,
response _hook : function ( response _data ) {
//this.pane.hide();
this . list . empty ( ) ;
} ,
show _hook : function ( response _data ) {
// remove anything next to list - added by other suggest plugins
this . status _select ( ) ;
} ,
position : function ( ) {
var p = this . pane ,
o = this . options ;
if ( o . parent ) {
return ;
}
if ( ! self . _position ) {
var inp = this . input ,
pos = inp . offset ( ) ,
input _width = inp . outerWidth ( true ) ,
input _height = inp . outerHeight ( true ) ;
pos . top += input _height ;
// show to calc dimensions
var pane _width = p . outerWidth ( ) ,
pane _height = p . outerHeight ( ) ,
pane _right = pos . left + pane _width ,
pane _bottom = pos . top + pane _height ,
pane _half = pos . top + pane _height / 2 ,
scroll _left = $ ( window ) . scrollLeft ( ) ,
scroll _top = $ ( window ) . scrollTop ( ) ,
window _width = $ ( window ) . width ( ) ,
window _height = $ ( window ) . height ( ) ,
window _right = window _width + scroll _left ,
window _bottom = window _height + scroll _top ;
// is input left or right side of window?
var left = true ;
if ( 'left' == o . align ) {
left = true ;
}
else if ( 'right' == o . align ) {
left = false ;
}
else if ( pos . left > ( scroll _left + window _width / 2 ) ) {
left = false ;
}
if ( ! left ) {
left = pos . left - ( pane _width - input _width ) ;
if ( left > scroll _left ) {
pos . left = left ;
}
}
if ( pane _half > window _bottom ) {
// can we see at least half of the list?
var top = pos . top - input _height - pane _height ;
if ( top > scroll _top ) {
pos . top = top ;
}
}
this . _position = pos ;
}
p . css ( { top : this . _position . top , left : this . _position . left } ) ;
} ,
ignore _mouseover : function ( e ) {
this . _ignore _mouseover = true ;
var self = this ;
this . ignore _mouseover . timeout =
setTimeout ( function ( ) {
self . ignore _mouseover _reset ( ) ;
} , 1000 ) ;
} ,
ignore _mouseover _reset : function ( ) {
this . _ignore _mouseover = false ;
} ,
get _selected : function ( ) {
var selected = null ,
select _class = this . options . css . selected ;
$ ( "li" , this . list )
. each ( function ( ) {
var $this = $ ( this ) ;
if ( $this . hasClass ( select _class ) &&
$this . is ( ":visible" ) ) {
selected = $this ;
return false ;
}
} ) ;
return selected ;
} ,
onselect : function ( $selected , focus ) {
var data = $selected . data ( "data.suggest" ) ;
if ( data ) {
this . input . val ( data . name )
. data ( "data.suggest" , data )
. trigger ( "fb-select" , data ) ;
this . trackEvent ( this . name , "fb-select" , "index" ,
$selected . prevAll ( ) . length ) ;
}
} ,
trackEvent : function ( category , action , label , value ) {
this . input . trigger ( "fb-track-event" , {
category : category ,
action : action ,
label : label ,
value : value
} ) ;
console . log ( "trackEvent" , category , action , label , value ) ;
} ,
hide _all : function ( e ) {
this . pane . hide ( ) ;
this . input . trigger ( "fb-pane-hide" , this ) ;
}
} ;
$ . extend ( $ . suggest , {
defaults : {
status : [
'Start typing to get suggestions...' ,
'Searching...' ,
'Select an item from the list:' ,
'Sorry, something went wrong. Please try again later'
] ,
soft : false ,
nomatch : "no matches" ,
// CSS default class names
css : {
pane : "fbs-pane" ,
list : "fbs-list" ,
item : "fbs-item" ,
item _name : "fbs-item-name" ,
selected : "fbs-selected" ,
status : "fbs-status"
} ,
css _prefix : null ,
parent : null ,
// option to animate suggest list when shown
animate : false ,
zIndex : null
} ,
strongify : function ( str , substr ) {
// safely markup substr within str with <strong>
var strong ;
var index = str . toLowerCase ( ) . indexOf ( substr . toLowerCase ( ) ) ;
if ( index >= 0 ) {
var substr _len = substr . length ;
var pre = document . createTextNode ( str . substring ( 0 , index ) ) ;
var em = $ ( "<strong>" ) . text ( str . substring ( index , index + substr _len ) ) ;
var post = document . createTextNode ( str . substring ( index + substr _len ) ) ;
strong = $ ( "<div>" )
. append ( pre ) . append ( em ) . append ( post ) ;
}
else {
strong = $ ( "<div>" ) . text ( str ) ;
}
return strong ;
} ,
keyCode : {
//BACKSPACE: 8,
CAPS _LOCK : 20 ,
//COMMA: 188,
CONTROL : 17 ,
//DELETE: 46,
DOWN : 40 ,
END : 35 ,
ENTER : 13 ,
ESCAPE : 27 ,
HOME : 36 ,
INSERT : 45 ,
LEFT : 37 ,
//NUMPAD_ADD: 107,
//NUMPAD_DECIMAL: 110,
//NUMPAD_DIVIDE: 111,
NUMPAD _ENTER : 108 ,
//NUMPAD_MULTIPLY: 106,
//NUMPAD_SUBTRACT: 109,
PAGE _DOWN : 34 ,
PAGE _UP : 33 ,
//PERIOD: 190,
RIGHT : 39 ,
SHIFT : 16 ,
SPACE : 32 ,
TAB : 9 ,
UP : 38 ,
OPTION : 18 ,
APPLE : 224
} ,
is _char : function ( e ) {
if ( e . type === "keypress" ) {
if ( ( e . metaKey || e . ctrlKey ) && e . charCode === 118 ) {
// ctrl+v
return true ;
}
else if ( "isChar" in e ) {
return e . isChar ;
}
}
else {
var not _char = $ . suggest . keyCode . not _char ;
if ( ! not _char ) {
not _char = { } ;
$ . each ( $ . suggest . keyCode , function ( k , v ) {
not _char [ '' + v ] = 1 ;
} ) ;
$ . suggest . keyCode . not _char = not _char ;
}
return ! ( ( '' + e . keyCode ) in not _char ) ;
}
} ,
/ * *
* Parse input string into actual query string and structured name : value list
*
* "bob dylan type:artist" - > [ "bob dylan" , [ "type:artist" ] ]
* "Dear... type:film name{full}:Dear..." - > [ "Dear..." , [ "type:film" , "name{full}:Dear..." ] ]
* /
parse _input : function ( str ) {
// only pick out valid name:value pairs
// a name:value is valid
// 1. if there are no spaces before/after ":"
// 2. name does not have any spaces
// 3. value does not have any spaces OR value is double quoted
var regex = /(\S+)\:(?:\"([^\"]+)\"|(\S+))/g ;
var qstr = str ;
var filters = [ ] ;
var overrides = { } ;
var m = regex . exec ( str ) ;
while ( m ) {
if ( m [ 1 ] in SEARCH _PARAMS ) {
overrides [ m [ 1 ] ] = $ . isEmptyObject ( m [ 2 ] ) ? m [ 3 ] : m [ 2 ] ;
}
else {
filters . push ( m [ 0 ] ) ;
}
qstr = qstr . replace ( m [ 0 ] , "" ) ;
m = regex . exec ( str ) ;
}
qstr = $ . trim ( qstr . replace ( /\s+/g , " " ) ) ;
return [ qstr , filters , overrides ] ;
} ,
/ * *
* Convenient methods and regexs to determine valid mql ids .
* /
mqlkey _fast : /^[_A-Za-z0-9][A-Za-z0-9_-]*$/ ,
mqlkey _slow : /^(?:[A-Za-z0-9]|\$[A-F0-9]{4})(?:[A-Za-z0-9_-]|\$[A-F0-9]{4})*$/ ,
check _mql _key : function ( val ) {
if ( $ . suggest . mqlkey _fast . test ( val ) ) {
return true ;
}
else if ( $ . suggest . mqlkey _slow . test ( val ) ) {
return true ;
}
return false ;
} ,
check _mql _id : function ( val ) {
if ( val . indexOf ( "/" ) === 0 ) {
var keys = val . split ( "/" ) ;
// remove beginning '/'
keys . shift ( ) ;
if ( keys . length == 1 && keys [ 0 ] === "" ) {
// "/" is a valid id
return true ;
}
else {
for ( var i = 0 , l = keys . length ; i < l ; i ++ ) {
if ( ! $ . suggest . check _mql _key ( keys [ i ] ) ) {
return false ;
}
}
return true ;
}
}
else {
return false ;
}
}
} ) ;
// some base implementation that we overwrite but want to call
var base = {
_destroy : $ . suggest . prototype . _destroy ,
show _hook : $ . suggest . prototype . show _hook
} ;
// *THE* Freebase suggest implementation
$ . suggest ( "suggest" , {
_init : function ( ) {
var self = this ,
o = this . options ;
if ( o . flyout _service _url == null ) {
o . flyout _service _url = o . service _url ;
}
this . flyout _url = o . flyout _service _url ;
if ( o . flyout _service _path ) {
this . flyout _url += o . flyout _service _path ;
}
// set api key for flyout/topic api
this . flyout _url = this . flyout _url . replace ( /\$\{key\}/g , o . key ) ;
if ( o . flyout _image _service _url == null ) {
o . flyout _image _service _url = o . service _url ;
}
this . flyout _image _url = o . flyout _image _service _url ;
if ( o . flyout _image _service _path ) {
this . flyout _image _url += o . flyout _image _service _path ;
}
// set api key for image api
this . flyout _image _url = this . flyout _image _url . replace ( /\$\{key\}/g , o . key ) ;
if ( ! $ . suggest . cache ) {
$ . suggest . cache = { } ;
}
if ( o . flyout ) {
this . flyoutpane = $ ( '<div style="display:none;" class="fbs-reset">' )
. addClass ( o . css . flyoutpane ) ;
if ( o . flyout _parent ) {
$ ( o . flyout _parent ) . append ( this . flyoutpane ) ;
}
else {
this . flyoutpane . css ( "position" , "absolute" ) ;
if ( o . zIndex ) {
this . flyoutpane . css ( "z-index" , o . zIndex ) ;
}
$ ( document . body ) . append ( this . flyoutpane ) ;
}
var hoverover = function ( e ) {
self . hoverover _list ( e ) ;
} ;
var hoverout = function ( e ) {
self . hoverout _list ( e ) ;
} ;
this . flyoutpane . hover ( hoverover , hoverout )
. bind ( "mousedown.suggest" , function ( e ) {
e . stopPropagation ( ) ;
self . pane . click ( ) ;
} ) ;
if ( ! $ . suggest . flyout ) {
$ . suggest . flyout = { } ;
}
if ( ! $ . suggest . flyout . cache ) {
$ . suggest . flyout . cache = { } ;
}
}
} ,
_destroy : function ( ) {
base . _destroy . call ( this ) ;
if ( this . flyoutpane ) {
this . flyoutpane . remove ( ) ;
}
this . input . removeData ( "request.count.suggest" ) ;
this . input . removeData ( "flyout.request.count.suggest" ) ;
} ,
shift _enter : function ( e ) {
if ( this . options . suggest _new ) {
this . suggest _new ( ) ;
this . hide _all ( ) ;
}
} ,
hide _all : function ( e ) {
this . pane . hide ( ) ;
if ( this . flyoutpane ) {
this . flyoutpane . hide ( ) ;
}
this . input . trigger ( "fb-pane-hide" , this ) ;
this . input . trigger ( "fb-flyoutpane-hide" , this ) ;
} ,
request : function ( val , cursor ) {
var self = this ,
o = this . options ;
var query = val ;
var filter = o . ac _param . filter || [ ] ;
// SEARCH_PARAMS can be overridden inline
var extend _ac _param = null ;
if ( $ . type ( filter ) === "string" ) {
// the original filter may be a single filter param (string)
filter = [ filter ] ;
}
// clone original filters so that we don't modify it
filter = filter . slice ( ) ;
if ( o . advanced ) {
// parse out additional filters in input value
var structured = $ . suggest . parse _input ( query ) ;
query = structured [ 0 ] ;
if ( structured [ 1 ] . length ) {
// all advance filters are ANDs
filter . push ( "(all " + structured [ 1 ] . join ( " " ) + ")" ) ;
}
extend _ac _param = structured [ 2 ] ;
if ( $ . suggest . check _mql _id ( query ) ) {
// handle anything that looks like a valid mql id
filter . push ( "(all mid:\"" + query + "\")" ) ;
query = "" ;
}
}
var data = { } ;
data [ o . query _param _name ] = query ;
if ( cursor ) {
data . cursor = cursor ;
}
$ . extend ( data , o . ac _param , extend _ac _param ) ;
if ( filter . length ) {
data . filter = filter ;
}
var url = o . service _url + o . service _path + "?" + $ . param ( data , true ) ;
var cached = $ . suggest . cache [ url ] ;
if ( cached ) {
this . response ( cached , cursor ? cursor : - 1 , true ) ;
return ;
}
clearTimeout ( this . request . timeout ) ;
var ajax _options = {
url : o . service _url + o . service _path ,
data : data ,
traditional : true ,
beforeSend : function ( xhr ) {
var calls = self . input . data ( "request.count.suggest" ) || 0 ;
if ( ! calls ) {
self . trackEvent ( self . name , "start_session" ) ;
}
calls += 1 ;
self . trackEvent ( self . name , "request" , "count" , calls ) ;
self . input . data ( "request.count.suggest" , calls ) ;
} ,
success : function ( data ) {
// FIXME: bad mql_output param can end up here with data: {'error':{'code':400,'errors':[...],'message':'Type /type/object does not have property lang'}}
$ . suggest . cache [ url ] = data ;
data . prefix = val ; // keep track of prefix to match up response with input value
self . response ( data , cursor ? cursor : - 1 ) ;
} ,
error : function ( xhr ) {
self . status _error ( ) ;
self . trackEvent ( self . name , "request" , "error" , {
url : this . url ,
response : xhr ? xhr . responseText : ''
} ) ;
self . input . trigger ( "fb-error" , Array . prototype . slice . call ( arguments ) ) ;
} ,
complete : function ( xhr ) {
if ( xhr ) {
self . trackEvent ( self . name , "request" , "tid" ,
xhr . getResponseHeader ( "X-Metaweb-TID" ) ) ;
}
} ,
dataType : "jsonp" ,
cache : true
} ;
this . request . timeout = setTimeout ( function ( ) {
$ . ajax ( ajax _options ) ;
} , o . xhr _delay ) ;
} ,
create _item : function ( data , response _data ) {
var css = this . options . css ;
var li = $ ( "<li>" ) . addClass ( css . item ) ;
var label = $ ( "<label>" )
. append ( $ . suggest . strongify ( data . name || data . id , response _data . prefix ) ) ;
var name = $ ( "<div>" ) . addClass ( css . item _name )
. append ( label ) ;
if ( data . under ) {
$ ( ":first" , label ) . append ( $ ( "<small>" ) . text ( " (" + data . under + ")" ) ) ;
}
if ( this . options . scoring != null &&
this . options . scoring . toUpperCase ( ) === 'SCHEMA' ) {
$ ( ":first" , label ) . append ( $ ( "<small>" ) . text ( " (" + data . id + ")" ) ) ;
}
var types = data . type ;
li . append ( name ) ;
var nt = data . notable ;
var type = $ ( "<div>" ) . addClass ( css . item _type ) ;
if ( nt && nt . name ) {
type . text ( nt . name ) ;
}
else if ( this . options . show _id && data . id ) {
// display human readable id if no notable type
type . text ( data . id ) ;
}
name . prepend ( type ) ;
//console.log("create_item", li);
return li ;
} ,
mouseover _item _hook : function ( li ) {
var data = li . data ( "data.suggest" ) ;
if ( this . options . flyout ) {
if ( data ) {
this . flyout _request ( data ) ;
}
else {
//this.flyoutpane.hide();
}
}
} ,
check _response : function ( response _data ) {
return response _data . prefix === this . input . val ( ) ;
} ,
response _hook : function ( response _data , cursor ) {
if ( this . flyoutpane ) {
this . flyoutpane . hide ( ) ;
}
if ( cursor > 0 ) {
$ ( ".fbs-more" , this . pane ) . remove ( ) ;
}
else {
//this.pane.hide();
this . list . empty ( ) ;
}
} ,
show _hook : function ( response _data , cursor , first ) {
base . show _hook . apply ( this , [ response _data ] ) ;
var o = this . options ,
self = this ,
p = this . pane ,
l = this . list ,
result = response _data . result ,
more = $ ( ".fbs-more" , p ) ,
suggestnew = $ ( ".fbs-suggestnew" , p ) ,
status = $ ( ".fbs-status" , p ) ;
// spell/correction
var correction = response _data . correction ;
if ( correction && correction . length ) {
var spell _link = $ ( '<a class="fbs-spell-link" href="#">' ) . append ( correction [ 0 ] )
. bind ( "click.suggest" , function ( e ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
self . input . val ( correction [ 0 ] ) . trigger ( "textchange" ) ;
} ) ;
self . status
. empty ( )
. append ( "Search instead for " )
. append ( spell _link )
. show ( ) ;
}
// more
if ( result && result . length && "cursor" in response _data ) {
if ( ! more . length ) {
var more _link = $ ( '<a class="fbs-more-link" href="#" title="(Ctrl+m)">view more</a>' ) ;
more = $ ( '<div class="fbs-more">' ) . append ( more _link ) ;
more _link . bind ( "click.suggest" , function ( e ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
var m = $ ( this ) . parent ( ".fbs-more" ) ;
self . more ( m . data ( "cursor.suggest" ) ) ;
} ) ;
l . after ( more ) ;
}
more . data ( "cursor.suggest" , response _data . cursor ) ;
more . show ( ) ;
}
else {
more . remove ( ) ;
}
// suggest_new
if ( o . suggest _new ) {
if ( ! suggestnew . length ) {
// create suggestnew option
var button = $ ( '<button class="fbs-suggestnew-button">' ) ;
button . text ( o . suggest _new ) ;
suggestnew = $ ( '<div class="fbs-suggestnew">' )
. append ( '<div class="fbs-suggestnew-description">Your item not in the list?</div>' )
. append ( button )
. append ( '<span class="fbs-suggestnew-shortcut">(Shift+Enter)</span>' )
. bind ( "click.suggest" , function ( e ) {
e . stopPropagation ( ) ;
self . suggest _new ( e ) ;
} ) ;
p . append ( suggestnew ) ;
}
suggestnew . show ( ) ;
}
else {
suggestnew . remove ( ) ;
}
// scroll to first if clicked on "more"
if ( first && first . length && cursor > 0 ) {
var top = first . prevAll ( ) . length * first . outerHeight ( ) ;
var scrollTop = l . scrollTop ( ) ;
l . animate ( { scrollTop : top } , "slow" , function ( ) {
first . trigger ( "mouseover.suggest" ) ;
} ) ;
}
} ,
suggest _new : function ( e ) {
var v = this . input . val ( ) ;
if ( v === "" ) {
return ;
}
//console.log("suggest_new", v);
this . input
. data ( "data.suggest" , v )
. trigger ( "fb-select-new" , v ) ;
this . trackEvent ( this . name , "fb-select-new" , "index" , "new" ) ;
this . hide _all ( ) ;
} ,
more : function ( cursor ) {
if ( cursor ) {
var orig = this . input . data ( "original.suggest" ) ;
if ( orig !== null ) {
this . input . val ( orig ) ;
}
this . request ( this . input . val ( ) , cursor ) ;
this . trackEvent ( this . name , "more" , "cursor" , cursor ) ;
}
return false ;
} ,
flyout _request : function ( data ) {
var self = this ;
var o = this . options ;
var sug _data = this . flyoutpane . data ( "data.suggest" ) ;
if ( sug _data && data . id === sug _data . id ) {
if ( ! this . flyoutpane . is ( ":visible" ) ) {
var s = this . get _selected ( ) ;
this . flyout _position ( s ) ;
this . flyoutpane . show ( ) ;
this . input . trigger ( "fb-flyoutpane-show" , this ) ;
}
return ;
}
// check $.suggest.flyout.cache
var cached = $ . suggest . flyout . cache [ data . id ] ;
if ( cached && cached . id && cached . html ) {
// CLI-10009: use cached item only if id and html present
this . flyout _response ( cached ) ;
return ;
}
//this.flyoutpane.hide();
var flyout _id = data . id ;
var url = this . flyout _url . replace ( /\$\{id\}/g , data . id ) ;
var ajax _options = {
url : url ,
traditional : true ,
beforeSend : function ( xhr ) {
var calls = self . input . data ( "flyout.request.count.suggest" ) || 0 ;
calls += 1 ;
self . trackEvent ( self . name , "flyout.request" , "count" , calls ) ;
self . input . data ( "flyout.request.count.suggest" , calls ) ;
} ,
success : function ( data ) {
data [ "req:id" ] = flyout _id ;
data . html = $ . suggest . suggest . create _flyout ( data , self . flyout _image _url ) ;
$ . suggest . flyout . cache [ flyout _id ] = data ;
self . flyout _response ( data ) ;
} ,
error : function ( xhr ) {
self . trackEvent ( self . name , "flyout" , "error" , {
url : this . url ,
response : xhr ? xhr . responseText : ''
} ) ;
} ,
complete : function ( xhr ) {
if ( xhr ) {
self . trackEvent ( self . name , "flyout" , "tid" ,
xhr . getResponseHeader ( "X-Metaweb-TID" ) ) ;
}
} ,
dataType : "jsonp" ,
cache : true
} ;
if ( o . flyout _lang ) {
ajax _options . data = { lang : o . flyout _lang } ;
}
clearTimeout ( this . flyout _request . timeout ) ;
this . flyout _request . timeout =
setTimeout ( function ( ) {
$ . ajax ( ajax _options ) ;
} , o . xhr _delay ) ;
this . input . trigger ( "fb-request-flyout" , ajax _options ) ;
} ,
flyout _response : function ( data ) {
var o = this . options ,
p = this . pane ,
s = this . get _selected ( ) || [ ] ;
if ( p . is ( ":visible" ) && s . length ) {
var sug _data = s . data ( "data.suggest" ) ;
if ( sug _data && data [ "req:id" ] === sug _data . id && data . html ) {
this . flyoutpane . html ( data . html ) ;
this . flyout _position ( s ) ;
this . flyoutpane . show ( )
. data ( "data.suggest" , sug _data ) ;
this . input . trigger ( "fb-flyoutpane-show" , this ) ;
}
}
} ,
flyout _position : function ( $item ) {
if ( this . options . flyout _parent ) {
return ;
}
var p = this . pane ,
fp = this . flyoutpane ,
css = this . options . css ,
pos = undefined ,
old _pos = {
top : parseInt ( fp . css ( "top" ) , 10 ) ,
left : parseInt ( fp . css ( "left" ) , 10 )
} ,
pane _pos = p . offset ( ) ,
pane _width = p . outerWidth ( ) ,
flyout _height = fp . outerHeight ( ) ,
flyout _width = fp . outerWidth ( ) ;
if ( this . options . flyout === "bottom" ) {
// flyout position on top/bottom
pos = pane _pos ;
var input _pos = this . input . offset ( ) ;
if ( pane _pos . top < input _pos . top ) {
pos . top -= flyout _height ;
}
else {
pos . top += p . outerHeight ( ) ;
}
fp . addClass ( css . flyoutpane + "-bottom" ) ;
}
else {
pos = $item . offset ( ) ;
var item _height = $item . outerHeight ( ) ;
pos . left += pane _width ;
var flyout _right = pos . left + flyout _width ,
scroll _left = $ ( document . body ) . scrollLeft ( ) ,
window _right = $ ( window ) . width ( ) + scroll _left ;
pos . top = pos . top + item _height - flyout _height ;
if ( pos . top < pane _pos . top ) {
pos . top = pane _pos . top ;
}
if ( flyout _right > window _right ) {
var left = pos . left - ( pane _width + flyout _width ) ;
if ( left > scroll _left ) {
pos . left = left ;
}
}
fp . removeClass ( css . flyoutpane + "-bottom" ) ;
}
if ( ! ( pos . top === old _pos . top &&
pos . left === old _pos . left ) ) {
fp . css ( { top : pos . top , left : pos . left } ) ;
}
} ,
hoverout _list : function ( e ) {
if ( this . flyoutpane && ! this . get _selected ( ) ) {
this . flyoutpane . hide ( ) ;
}
}
} ) ;
// Freebase suggest settings
$ . extend ( $ . suggest . suggest , {
defaults : {
/ * *
* filter , spell , lang , exact , scoring , key , prefixed , stemmed , format
*
* are the new parameters used by the new freebase search on googleapis .
* Please refer the the API documentation as these parameters
* will be transparently passed through to the search service .
*
* @ see http : //wiki.freebase.com/wiki/ApiSearch
* /
// search filters
filter : null ,
// spelling corrections
spell : "always" ,
exact : false ,
scoring : null ,
// language to search (default to en)
lang : null , // NULL defaults to "en",
// API key: required for googleapis
key : null ,
prefixed : true ,
stemmed : null ,
format : null ,
// Enable structured input name:value pairs that get appended to the search filters
// For example:
//
// "bob dylan type:artist"
//
// Would get translated to the following request:
//
// /freebase/v1/search?query=bob+dylan&filter=<original filter>&filter=(all type:artist)
//
advanced : true ,
// If an item does not have a "notable" field, display the id or mid of the item
show _id : true ,
// query param name for the search service.
// If query name was "foo": search?foo=...
query _param _name : "query" ,
// base url for autocomplete service
service _url : "https://www.googleapis.com/freebase/v1" ,
// service_url + service_path = url to autocomplete service
service _path : "/search" ,
// 'left', 'right' or null
// where list will be aligned left or right with the input
align : null ,
// whether or not to show flyout on mouseover
flyout : true ,
// default is service_url if NULL
flyout _service _url : null ,
// flyout_service_url + flyout_service_path =
// url to topic api with filter=suggest
flyout _service _path : "/topic${id}?filter=suggest&filter=/common/topic/article&key=${key}" ,
// default is service_url if NULL
flyout _image _service _url : null ,
flyout _image _service _path : "/image${id}?maxwidth=75&key=${key}" ,
// jQuery selector to specify where the flyout
// will be appended to (defaults to document.body).
flyout _parent : null ,
// text snippet you want to show for the suggest
// new option
// clicking will trigger an fb-select-new event
// along with the input value
suggest _new : null ,
nomatch : {
title : "No suggested matches" ,
heading : "Tips on getting better suggestions:" ,
tips : [
"Enter more or fewer characters" ,
"Add words related to your original search" ,
"Try alternate spellings" ,
"Check your spelling"
]
} ,
// CSS default class names
css : {
item _type : "fbs-item-type" ,
flyoutpane : "fbs-flyout-pane"
} ,
// the delay before sending off the ajax request to the
// suggest and flyout service
xhr _delay : 200
} ,
// utility methods to get property, values from the topic api result
get _property : function ( topic _result , prop _id ) {
var props = topic _result . property ;
if ( props ) {
return props [ prop _id ] ;
}
else {
return null ;
}
} ,
get _values : function ( topic _result , prop _id ) {
var prop = $ . suggest . suggest . get _property ( topic _result , prop _id ) ;
if ( prop && prop . values ) {
return prop . values ;
}
else {
return null ;
}
} ,
get _first _value : function ( topic _result , prop _id ) {
var values = $ . suggest . suggest . get _values ( topic _result , prop _id ) ;
if ( values && values . length > 0 ) {
return values [ 0 ] ;
}
return null ;
} ,
/ * *
* Create the flyout html content given the topic api result
* ( i . e . , ... / topic / some / id ? filter = suggest ) . The resulting html
* will be cached for optimization .
*
* @ param data : Object - The topic api result with ? filter = suggest
* @ param flyout _image _url : Object - The url template for the image url .
* The substring , "${id}" , will be replaced by data . id . It is assumed all
* parameters to the flyout image service ( api key , dimensions , etc . ) is
* already encoded into the url template .
* /
create _flyout : function ( data , flyout _image _url ) {
var get _property = $ . suggest . suggest . get _property ;
var get _values = $ . suggest . suggest . get _values ;
var get _first _value = $ . suggest . suggest . get _first _value ;
var name = get _first _value ( data , "/type/object/name" ) ;
if ( name ) {
name = name . text ;
}
// prefer /common/topic/description over /common/topic/article
var article =
get _first _value ( data , "/common/topic/description" ) ||
get _first _value ( data , "/common/topic/article" ) || false ;
if ( article ) {
article = article . text ;
}
var image = get _first _value ( data , "/common/topic/image" ) || false ;
if ( image ) {
image = flyout _image _url . replace ( /\$\{id\}/g , data . id ) ;
}
var notable _props = [ ] ;
var notability = [ data . id ] ;
// notable (disambiguating) properties
var values = get _values ( data , "/common/topic/notable_properties" ) ;
if ( values ) {
$ . each ( values , function ( i , prop ) {
var p = get _property ( data , prop . id ) ;
if ( p && p . valuetype !== 'compound' ) {
var prop _values = get _values ( data , prop . id ) ;
if ( prop _values && prop _values . length ) {
prop _values = $ . map ( prop _values , function ( v ) {
return v . text ;
} ) . join ( ", " ) ;
notable _props . push ( [ prop . text || prop . id , prop _values ] ) ;
}
}
} ) ;
}
// notability (notable_for and notable_types)
var notable _for = get _values ( data , "/common/topic/notable_for" ) ;
var notable _types = get _values ( data , "/common/topic/notable_types" ) ;
if ( notable _for || notable _types ) {
var seen = { } ;
notability = [ ] ;
$ . each ( [ notable _for , notable _types ] , function ( i , notable ) {
if ( notable ) {
notable . forEach ( function ( n ) {
if ( n . id && ! seen [ n . id ] ) {
notability . push ( n . text ) ;
seen [ n . id ] = true ;
}
} ) ;
}
} ) ;
}
notability = notability . join ( ", " ) ;
var content = $ ( '<div class="fbs-flyout-content">' ) ;
if ( name ) {
content . append ( $ ( '<h1 id="fbs-flyout-title">' ) . text ( name ) ) ;
}
content . append ( $ ( '<h3 class="fbs-topic-properties fbs-flyout-id">' ) . text ( data . id ) ) ;
$ . each ( notable _props , function ( i , prop ) {
content . append ( $ ( '<h3 class="fbs-topic-properties">' )
. append ( $ ( '<strong>' ) . text ( prop [ 0 ] + ': ' ) )
. append ( document . createTextNode ( prop [ 1 ] ) ) ) ;
} ) ;
if ( article ) {
content . append ( $ ( '<p class="fbs-topic-article">' ) . text ( article ) ) ;
}
if ( image ) {
content . children ( ) . addClass ( 'fbs-flyout-image-true' ) ;
content . prepend ( $ ( '<img id="fbs-topic-image" class="fbs-flyout-image-true" src="' + image + '">' ) ) ;
}
var types = $ ( '<span class="fbs-flyout-types">' ) . text ( notability ) ;
var footer = $ ( '<div class="fbs-attribution">' ) . append ( types ) ;
return $ ( '<div>' )
. append ( content )
. append ( footer )
. html ( ) ;
}
} ) ;
var f = document . createElement ( "input" ) ;
} ) ( jQuery ) ;