diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2010-07-28 11:52:48 +0200 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2010-07-28 11:52:48 +0200 |
commit | 222b01f5169f1c7e69762e0e8904c24f78f71882 (patch) | |
tree | 8e932e12546bb991357ec48eb1638d1770be7a35 /skins/common/mwsuggest.js | |
parent | 00ab76a6b686e98a914afc1975812d2b1aaa7016 (diff) |
update to MediaWiki 1.16.0
Diffstat (limited to 'skins/common/mwsuggest.js')
-rw-r--r-- | skins/common/mwsuggest.js | 1312 |
1 files changed, 728 insertions, 584 deletions
diff --git a/skins/common/mwsuggest.js b/skins/common/mwsuggest.js index 061a6451..8f638c47 100644 --- a/skins/common/mwsuggest.js +++ b/skins/common/mwsuggest.js @@ -13,7 +13,6 @@ var os_map = {}; var os_cache = {}; // global variables for suggest_keypress var os_cur_keypressed = 0; -var os_last_keypress = 0; var os_keypressed_count = 0; // type: Timer var os_timer = null; @@ -41,34 +40,33 @@ var os_animation_delay = 30; var os_container_max_width = 2; // currently active animation timer var os_animation_timer = null; +/** + * <datalist> is a new HTML5 element that allows you to manually supply + * suggestion lists and have them rendered according to the right platform + * conventions. However, the only shipping browser as of early 2010 is Opera, + * and that has a fatal problem: the suggestion lags behind what the user types + * by one keypress. (Reported as DSK-276870 to Opera's secret bug tracker.) + * The code here otherwise seems to work, though, so this can be flipped on + * (maybe with a UA check) when some browser has a better implementation. + */ +// var os_use_datalist = 'list' in document.createElement( 'input' ); +var os_use_datalist = false; /** Timeout timer class that will fetch the results */ -function os_Timer(id,r,query){ +function os_Timer( id, r, query ) { this.id = id; this.r = r; this.query = query; } -/** Timer user to animate expansion/contraction of container width */ -function os_AnimationTimer(r, target){ - this.r = r; - var current = document.getElementById(r.container).offsetWidth; - this.inc = Math.round((target-current) / os_animation_steps); - if(this.inc < os_animation_min_step && this.inc >=0) - this.inc = os_animation_min_step; // minimal animation step - if(this.inc > -os_animation_min_step && this.inc <0) - this.inc = -os_animation_min_step; - this.target = target; -} - /** Property class for single search box */ -function os_Results(name, formname){ +function os_Results( name, formname ) { this.searchform = formname; // id of the searchform this.searchbox = name; // id of the searchbox - this.container = name+"Suggest"; // div that holds results - this.resultTable = name+"Result"; // id base for the result table (+num = table row) - this.resultText = name+"ResultText"; // id base for the spans within result tables (+num) - this.toggle = name+"Toggle"; // div that has the toggle (enable/disable) link + this.container = name + 'Suggest'; // div that holds results + this.resultTable = name + 'Result'; // id base for the result table (+num = table row) + this.resultText = name + 'ResultText'; // id base for the spans within result tables (+num) + this.toggle = name + 'Toggle'; // div that has the toggle (enable/disable) link this.query = null; // last processed query this.results = null; // parsed titles this.resultCount = 0; // number of results @@ -78,774 +76,920 @@ function os_Results(name, formname){ this.containerRow = 0; // height of result field in the container this.containerTotal = 0; // total height of the container will all results this.visible = false; // if container is visible + this.stayHidden = false; // don't try to show if lost focus +} + +/** Timer user to animate expansion/contraction of container width */ +function os_AnimationTimer( r, target ) { + this.r = r; + var current = document.getElementById(r.container).offsetWidth; + this.inc = Math.round( ( target - current ) / os_animation_steps ); + if( this.inc < os_animation_min_step && this.inc >=0 ) { + this.inc = os_animation_min_step; // minimal animation step + } + if( this.inc > -os_animation_min_step && this.inc < 0 ) { + this.inc = -os_animation_min_step; + } + this.target = target; +} + +/****************** + * Initialization + ******************/ + +/** Initialization, call upon page onload */ +function os_MWSuggestInit() { + for( i = 0; i < os_autoload_inputs.length; i++ ) { + var id = os_autoload_inputs[i]; + var form = os_autoload_forms[i]; + element = document.getElementById( id ); + if( element != null ) { + os_initHandlers( id, form, element ); + } + } +} + +/** Init Result objects and event handlers */ +function os_initHandlers( name, formname, element ) { + var r = new os_Results( name, formname ); + // event handler + os_hookEvent( element, 'keyup', function( event ) { os_eventKeyup( event ); } ); + os_hookEvent( element, 'keydown', function( event ) { os_eventKeydown( event ); } ); + os_hookEvent( element, 'keypress', function( event ) { os_eventKeypress( event ); } ); + if ( !os_use_datalist ) { + // These are needed for the div hack to hide it if the user blurs. + os_hookEvent( element, 'blur', function( event ) { os_eventBlur( event ); } ); + os_hookEvent( element, 'focus', function( event ) { os_eventFocus( event ); } ); + // We don't want browser auto-suggestions interfering with our div, but + // autocomplete must be on for datalist to work (at least in Opera + // 10.10). + element.setAttribute( 'autocomplete', 'off' ); + } + // stopping handler + os_hookEvent( document.getElementById( formname ), 'submit', function( event ) { return os_eventOnsubmit( event ); } ); + os_map[name] = r; + // toggle link + if( document.getElementById( r.toggle ) == null ) { + // TODO: disable this while we figure out a way for this to work in all browsers + /* if( name == 'searchInput' ) { + // special case: place above the main search box + var t = os_createToggle( r, 'os-suggest-toggle' ); + var searchBody = document.getElementById( 'searchBody' ); + var first = searchBody.parentNode.firstChild.nextSibling.appendChild(t); + } else { + // default: place below search box to the right + var t = os_createToggle( r, 'os-suggest-toggle-def' ); + var top = element.offsetTop + element.offsetHeight; + var left = element.offsetLeft + element.offsetWidth; + t.style.position = 'absolute'; + t.style.top = top + 'px'; + t.style.left = left + 'px'; + element.parentNode.appendChild( t ); + // only now width gets calculated, shift right + left -= t.offsetWidth; + t.style.left = left + 'px'; + t.style.visibility = 'visible'; + } */ + } + +} + +function os_hookEvent( element, hookName, hookFunct ) { + if ( element.addEventListener ) { + element.addEventListener( hookName, hookFunct, false ); + } else if ( window.attachEvent ) { + element.attachEvent( 'on' + hookName, hookFunct ); + } +} + +/******************** + * Keyboard events + ********************/ + +/** Event handler that will fetch results on keyup */ +function os_eventKeyup( e ) { + var targ = os_getTarget( e ); + var r = os_map[targ.id]; + if( r == null ) { + return; // not our event + } + + // some browsers won't generate keypressed for arrow keys, catch it + if( os_keypressed_count == 0 ) { + os_processKey( r, os_cur_keypressed, targ ); + } + var query = targ.value; + os_fetchResults( r, query, os_search_timeout ); +} + +/** catch arrows up/down and escape to hide the suggestions */ +function os_processKey( r, keypressed, targ ) { + if ( keypressed == 40 && !r.visible && os_timer == null ) { + // If the user hits the down arrow, fetch results immediately if none + // are already displayed. + r.query = ''; + os_fetchResults( r, targ.value, 0 ); + } + // Otherwise, if we're not using datalist, we need to handle scrolling and + // so on. + if ( os_use_datalist ) { + return; + } + if ( keypressed == 40 ) { // Arrow Down + if ( r.visible ) { + os_changeHighlight( r, r.selected, r.selected + 1, true ); + } + } else if ( keypressed == 38 ) { // Arrow Up + if ( r.visible ) { + os_changeHighlight( r, r.selected, r.selected - 1, true ); + } + } else if( keypressed == 27 ) { // Escape + document.getElementById( r.searchbox ).value = r.original; + r.query = r.original; + os_hideResults( r ); + } else if( r.query != document.getElementById( r.searchbox ).value ) { + // os_hideResults( r ); // don't show old suggestions + } } -/** Hide results div */ -function os_hideResults(r){ - var c = document.getElementById(r.container); - if(c != null) - c.style.visibility = "hidden"; +/** When keys is held down use a timer to output regular events */ +function os_eventKeypress( e ) { + var targ = os_getTarget( e ); + var r = os_map[targ.id]; + if( r == null ) { + return; // not our event + } + + var keypressed = os_cur_keypressed; + + os_keypressed_count++; + os_processKey( r, keypressed, targ ); +} + +/** Catch the key code (Firefox bug) */ +function os_eventKeydown( e ) { + if ( !e ) { + e = window.event; + } + var targ = os_getTarget( e ); + var r = os_map[targ.id]; + if( r == null ) { + return; // not our event + } + + os_mouse_moved = false; + + os_cur_keypressed = ( e.keyCode == undefined ) ? e.which : e.keyCode; + os_keypressed_count = 0; +} + + +/** When the form is submitted hide everything, cancel updates... */ +function os_eventOnsubmit( e ) { + var targ = os_getTarget( e ); + + os_is_stopped = true; + // kill timed requests + if( os_timer != null && os_timer.id != null ) { + clearTimeout( os_timer.id ); + os_timer = null; + } + // Hide all suggestions + for( i = 0; i < os_autoload_inputs.length; i++ ) { + var r = os_map[os_autoload_inputs[i]]; + if( r != null ) { + var b = document.getElementById( r.searchform ); + if( b != null && b == targ ) { + // set query value so the handler won't try to fetch additional results + r.query = document.getElementById( r.searchbox ).value; + } + os_hideResults( r ); + } + } + return true; +} + + + +/** Hide results from the user, either making the div visibility=hidden or + * detaching the datalist from the input. */ +function os_hideResults( r ) { + if ( os_use_datalist ) { + document.getElementById( r.searchbox ).setAttribute( 'list', '' ); + } else { + var c = document.getElementById( r.container ); + if ( c != null ) { + c.style.visibility = 'hidden'; + } + } r.visible = false; r.selected = -1; } +function os_decodeValue( value ) { + if ( decodeURIComponent ) { + return decodeURIComponent( value ); + } + if( unescape ) { + return unescape( value ); + } + return null; +} + +function os_encodeQuery( value ) { + if ( encodeURIComponent ) { + return encodeURIComponent( value ); + } + if( escape ) { + return escape( value ); + } + return null; +} + +/** Handles data from XMLHttpRequest, and updates the suggest results */ +function os_updateResults( r, query, text, cacheKey ) { + os_cache[cacheKey] = text; + r.query = query; + r.original = query; + if( text == '' ) { + r.results = null; + r.resultCount = 0; + os_hideResults( r ); + } else { + try { + var p = eval( '(' + text + ')' ); // simple json parse, could do a safer one + if( p.length < 2 || p[1].length == 0 ) { + r.results = null; + r.resultCount = 0; + os_hideResults( r ); + return; + } + if ( os_use_datalist ) { + os_setupDatalist( r, p[1] ); + } else { + os_setupDiv( r, p[1] ); + } + } catch( e ) { + // bad response from server or such + os_hideResults( r ); + os_cache[cacheKey] = null; + } + } +} + +/** + * Create and populate a <datalist>. + * + * @param r os_Result object + * @param results Array of the new results to replace existing ones + */ +function os_setupDatalist( r, results ) { + var s = document.getElementById( r.searchbox ); + var c = document.getElementById( r.container ); + if ( c == null ) { + c = document.createElement( 'datalist' ); + c.setAttribute( 'id', r.container ); + document.body.appendChild( c ); + } else { + c.innerHTML = ''; + } + s.setAttribute( 'list', r.container ); + + r.results = new Array(); + r.resultCount = results.length; + r.visible = true; + for ( i = 0; i < results.length; i++ ) { + var title = os_decodeValue( results[i] ); + var opt = document.createElement( 'option' ); + opt.value = title; + r.results[i] = title; + c.appendChild( opt ); + } +} + +/** Fetch namespaces from checkboxes or hidden fields in the search form, + if none defined use wgSearchNamespaces global */ +function os_getNamespaces( r ) { + var namespaces = ''; + var elements = document.forms[r.searchform].elements; + for( i = 0; i < elements.length; i++ ) { + var name = elements[i].name; + if( typeof name != 'undefined' && name.length > 2 && name[0] == 'n' && + name[1] == 's' && ( + ( elements[i].type == 'checkbox' && elements[i].checked ) || + ( elements[i].type == 'hidden' && elements[i].value == '1' ) + ) + ) { + if( namespaces != '' ) { + namespaces += '|'; + } + namespaces += name.substring( 2 ); + } + } + if( namespaces == '' ) { + namespaces = wgSearchNamespaces.join('|'); + } + return namespaces; +} + +/** Update results if user hasn't already typed something else */ +function os_updateIfRelevant( r, query, text, cacheKey ) { + var t = document.getElementById( r.searchbox ); + if( t != null && t.value == query ) { // check if response is still relevant + os_updateResults( r, query, text, cacheKey ); + } + r.query = query; +} + +/** Fetch results after some timeout */ +function os_delayedFetch() { + if( os_timer == null ) { + return; + } + var r = os_timer.r; + var query = os_timer.query; + os_timer = null; + var path = wgMWSuggestTemplate.replace( "{namespaces}", os_getNamespaces( r ) ) + .replace( "{dbname}", wgDBname ) + .replace( "{searchTerms}", os_encodeQuery( query ) ); + + // try to get from cache, if not fetch using ajax + var cached = os_cache[path]; + if( cached != null && cached != undefined ) { + os_updateIfRelevant( r, query, cached, path ); + } else { + var xmlhttp = sajax_init_object(); + if( xmlhttp ) { + try { + xmlhttp.open( 'GET', path, true ); + xmlhttp.onreadystatechange = function() { + if ( xmlhttp.readyState == 4 && typeof os_updateIfRelevant == 'function' ) { + os_updateIfRelevant( r, query, xmlhttp.responseText, path ); + } + }; + xmlhttp.send( null ); + } catch ( e ) { + if ( window.location.hostname == 'localhost' ) { + alert( "Your browser blocks XMLHttpRequest to 'localhost', try using a real hostname for development/testing." ); + } + throw e; + } + } + } +} + +/** Init timed update via os_delayedUpdate() */ +function os_fetchResults( r, query, timeout ) { + if( query == '' ) { + r.query = ''; + os_hideResults( r ); + return; + } else if( query == r.query ) { + return; // no change + } + + os_is_stopped = false; // make sure we're running + + // cancel any pending fetches + if( os_timer != null && os_timer.id != null ) { + clearTimeout( os_timer.id ); + } + // schedule delayed fetching of results + if( timeout != 0 ) { + os_timer = new os_Timer( setTimeout( "os_delayedFetch()", timeout ), r, query ); + } else { + os_timer = new os_Timer( null, r, query ); + os_delayedFetch(); // do it now! + } +} + +/** Find event target */ +function os_getTarget( e ) { + if ( !e ) { + e = window.event; + } + if ( e.target ) { + return e.target; + } else if ( e.srcElement ) { + return e.srcElement; + } else { + return null; + } +} + +/** Check if x is a valid integer */ +function os_isNumber( x ) { + if( x == '' || isNaN( x ) ) { + return false; + } + for( var i = 0; i < x.length; i++ ) { + var c = x.charAt( i ); + if( !( c >= '0' && c <= '9' ) ) { + return false; + } + } + return true; +} + +/** Call this to enable suggestions on input (id=inputId), on a form (name=formName) */ +function os_enableSuggestionsOn( inputId, formName ) { + os_initHandlers( inputId, formName, document.getElementById( inputId ) ); +} + +/** Call this to disable suggestios on input box (id=inputId) */ +function os_disableSuggestionsOn( inputId ) { + r = os_map[inputId]; + if( r != null ) { + // cancel/hide results + os_timer = null; + os_hideResults( r ); + // turn autocomplete on ! + document.getElementById( inputId ).setAttribute( 'autocomplete', 'on' ); + // remove descriptor + os_map[inputId] = null; + } + + // Remove the element from the os_autoload_* arrays + var index = os_autoload_inputs.indexOf( inputId ); + if ( index >= 0 ) { + os_autoload_inputs[index] = os_autoload_forms[index] = ''; + } +} + +/************************************************ + * Div-only functions (irrelevant for datalist) + ************************************************/ + +/** Event: loss of focus of input box */ +function os_eventBlur( e ) { + var targ = os_getTarget( e ); + var r = os_map[targ.id]; + if( r == null ) { + return; // not our event + } + if( !os_mouse_pressed ) { + os_hideResults( r ); + // force canvas to stay hidden + r.stayHidden = true; + // cancel any pending fetches + if( os_timer != null && os_timer.id != null ) { + clearTimeout( os_timer.id ); + } + os_timer = null; + } +} + +/** Event: focus (catch only when stopped) */ +function os_eventFocus( e ) { + var targ = os_getTarget( e ); + var r = os_map[targ.id]; + if( r == null ) { + return; // not our event + } + r.stayHidden = false; +} + +/** + * Create and populate a <div>, for non-<datalist>-supporting browsers. + * + * @param r os_Result object + * @param results Array of the new results to replace existing ones + */ +function os_setupDiv( r, results ) { + var c = document.getElementById( r.container ); + if ( c == null ) { + c = os_createContainer( r ); + } + c.innerHTML = os_createResultTable( r, results ); + // init container table sizes + var t = document.getElementById( r.resultTable ); + r.containerTotal = t.offsetHeight; + r.containerRow = t.offsetHeight / r.resultCount; + os_fitContainer( r ); + os_trimResultText( r ); + os_showResults( r ); +} + +/** Create the result table to be placed in the container div */ +function os_createResultTable( r, results ) { + var c = document.getElementById( r.container ); + var width = c.offsetWidth - os_operaWidthFix( c.offsetWidth ); + var html = '<table class="os-suggest-results" id="' + r.resultTable + '" style="width: ' + width + 'px;">'; + r.results = new Array(); + r.resultCount = results.length; + for( i = 0; i < results.length; i++ ) { + var title = os_decodeValue( results[i] ); + r.results[i] = title; + html += '<tr><td class="os-suggest-result" id="' + r.resultTable + i + '"><span id="' + r.resultText + i + '">' + title + '</span></td></tr>'; + } + html += '</table>'; + return html; +} + /** Show results div */ -function os_showResults(r){ - if(os_is_stopped) +function os_showResults( r ) { + if( os_is_stopped ) { + return; + } + if( r.stayHidden ) { return; - os_fitContainer(r); - var c = document.getElementById(r.container); + } + os_fitContainer( r ); + var c = document.getElementById( r.container ); r.selected = -1; - if(c != null){ + if( c != null ) { c.scrollTop = 0; - c.style.visibility = "visible"; + c.style.visibility = 'visible'; r.visible = true; } } -function os_operaWidthFix(x){ - // TODO: better css2 incompatibility detection here - if(is_opera || is_khtml || navigator.userAgent.toLowerCase().indexOf('firefox/1')!=-1){ - return 30; // opera&konqueror & old firefox don't understand overflow-x, estimate scrollbar width +function os_operaWidthFix( x ) { + // For browsers that don't understand overflow-x, estimate scrollbar width + if( typeof document.body.style.overflowX != 'string' ) { + return 30; } return 0; } -function os_encodeQuery(value){ - if (encodeURIComponent) { - return encodeURIComponent(value); - } - if(escape) { - return escape(value); - } - return null; -} -function os_decodeValue(value){ - if (decodeURIComponent) { - return decodeURIComponent(value); - } - if(unescape){ - return unescape(value); - } - return null; -} - /** Brower-dependent functions to find window inner size, and scroll status */ function f_clientWidth() { - return f_filterResults ( + return f_filterResults( window.innerWidth ? window.innerWidth : 0, document.documentElement ? document.documentElement.clientWidth : 0, document.body ? document.body.clientWidth : 0 ); } + function f_clientHeight() { - return f_filterResults ( + return f_filterResults( window.innerHeight ? window.innerHeight : 0, document.documentElement ? document.documentElement.clientHeight : 0, document.body ? document.body.clientHeight : 0 ); } + function f_scrollLeft() { - return f_filterResults ( + return f_filterResults( window.pageXOffset ? window.pageXOffset : 0, document.documentElement ? document.documentElement.scrollLeft : 0, document.body ? document.body.scrollLeft : 0 ); } + function f_scrollTop() { - return f_filterResults ( + return f_filterResults( window.pageYOffset ? window.pageYOffset : 0, document.documentElement ? document.documentElement.scrollTop : 0, document.body ? document.body.scrollTop : 0 ); } -function f_filterResults(n_win, n_docel, n_body) { + +function f_filterResults( n_win, n_docel, n_body ) { var n_result = n_win ? n_win : 0; - if (n_docel && (!n_result || (n_result > n_docel))) + if ( n_docel && ( !n_result || ( n_result > n_docel ) ) ) { n_result = n_docel; - return n_body && (!n_result || (n_result > n_body)) ? n_body : n_result; + } + return n_body && ( !n_result || ( n_result > n_body ) ) ? n_body : n_result; } /** Get the height available for the results container */ -function os_availableHeight(r){ - var absTop = document.getElementById(r.container).style.top; - var px = absTop.lastIndexOf("px"); - if(px > 0) - absTop = absTop.substring(0,px); - return f_clientHeight() - (absTop - f_scrollTop()); +function os_availableHeight( r ) { + var absTop = document.getElementById( r.container ).style.top; + var px = absTop.lastIndexOf( 'px' ); + if( px > 0 ) { + absTop = absTop.substring( 0, px ); + } + return f_clientHeight() - ( absTop - f_scrollTop() ); } - /** Get element absolute position {left,top} */ -function os_getElementPosition(elemID){ - var offsetTrail = document.getElementById(elemID); +function os_getElementPosition( elemID ) { + var offsetTrail = document.getElementById( elemID ); var offsetLeft = 0; var offsetTop = 0; - while (offsetTrail){ + while ( offsetTrail ) { offsetLeft += offsetTrail.offsetLeft; offsetTop += offsetTrail.offsetTop; offsetTrail = offsetTrail.offsetParent; } - if (navigator.userAgent.indexOf('Mac') != -1 && typeof document.body.leftMargin != 'undefined'){ + if ( navigator.userAgent.indexOf('Mac') != -1 && typeof document.body.leftMargin != 'undefined' ) { offsetLeft += document.body.leftMargin; offsetTop += document.body.topMargin; } - return {left:offsetLeft,top:offsetTop}; + return { left:offsetLeft, top:offsetTop }; } /** Create the container div that will hold the suggested titles */ -function os_createContainer(r){ - var c = document.createElement("div"); - var s = document.getElementById(r.searchbox); - var pos = os_getElementPosition(r.searchbox); +function os_createContainer( r ) { + var c = document.createElement( 'div' ); + var s = document.getElementById( r.searchbox ); + var pos = os_getElementPosition( r.searchbox ); var left = pos.left; var top = pos.top + s.offsetHeight; - c.className = "os-suggest"; - c.setAttribute("id", r.container); - document.body.appendChild(c); + c.className = 'os-suggest'; + c.setAttribute( 'id', r.container ); + document.body.appendChild( c ); // dynamically generated style params // IE workaround, cannot explicitely set "style" attribute - c = document.getElementById(r.container); - c.style.top = top+"px"; - c.style.left = left+"px"; - c.style.width = s.offsetWidth+"px"; + c = document.getElementById( r.container ); + c.style.top = top + 'px'; + c.style.left = left + 'px'; + c.style.width = s.offsetWidth + 'px'; // mouse event handlers - c.onmouseover = function(event) { os_eventMouseover(r.searchbox, event); }; - c.onmousemove = function(event) { os_eventMousemove(r.searchbox, event); }; - c.onmousedown = function(event) { return os_eventMousedown(r.searchbox, event); }; - c.onmouseup = function(event) { os_eventMouseup(r.searchbox, event); }; + c.onmouseover = function( event ) { os_eventMouseover( r.searchbox, event ); }; + c.onmousemove = function( event ) { os_eventMousemove( r.searchbox, event ); }; + c.onmousedown = function( event ) { return os_eventMousedown( r.searchbox, event ); }; + c.onmouseup = function( event ) { os_eventMouseup( r.searchbox, event ); }; return c; } /** change container height to fit to screen */ -function os_fitContainer(r){ - var c = document.getElementById(r.container); - var h = os_availableHeight(r) - 20; +function os_fitContainer( r ) { + var c = document.getElementById( r.container ); + var h = os_availableHeight( r ) - 20; var inc = r.containerRow; - h = parseInt(h/inc) * inc; - if(h < (2 * inc) && r.resultCount > 1) // min: two results + h = parseInt( h / inc ) * inc; + if( h < ( 2 * inc ) && r.resultCount > 1 ) { // min: two results h = 2 * inc; - if((h/inc) > os_max_lines_per_suggest ) + } + if( ( h / inc ) > os_max_lines_per_suggest ) { h = inc * os_max_lines_per_suggest; - if(h < r.containerTotal){ - c.style.height = h +"px"; - r.containerCount = parseInt(Math.round(h/inc)); - } else{ - c.style.height = r.containerTotal+"px"; + } + if( h < r.containerTotal ) { + c.style.height = h + 'px'; + r.containerCount = parseInt( Math.round( h / inc ) ); + } else { + c.style.height = r.containerTotal + 'px'; r.containerCount = r.resultCount; } } + /** If some entries are longer than the box, replace text with "..." */ -function os_trimResultText(r){ +function os_trimResultText( r ) { // find max width, first see if we could expand the container to fit it var maxW = 0; - for(var i=0;i<r.resultCount;i++){ - var e = document.getElementById(r.resultText+i); - if(e.offsetWidth > maxW) + for( var i = 0; i < r.resultCount; i++ ) { + var e = document.getElementById( r.resultText + i ); + if( e.offsetWidth > maxW ) { maxW = e.offsetWidth; + } } - var w = document.getElementById(r.container).offsetWidth; + var w = document.getElementById( r.container ).offsetWidth; var fix = 0; - if(r.containerCount < r.resultCount){ + if( r.containerCount < r.resultCount ) { fix = 20; // give 20px for scrollbar - } else - fix = os_operaWidthFix(w); - if(fix < 4) + } else { + fix = os_operaWidthFix( w ); + } + if( fix < 4 ) { fix = 4; // basic padding + } maxW += fix; // resize container to fit more data if permitted - var normW = document.getElementById(r.searchbox).offsetWidth; + var normW = document.getElementById( r.searchbox ).offsetWidth; var prop = maxW / normW; - if(prop > os_container_max_width) + if( prop > os_container_max_width ) { prop = os_container_max_width; - else if(prop < 1) + } else if( prop < 1 ) { prop = 1; + } var newW = Math.round( normW * prop ); - if( w != newW ){ + if( w != newW ) { w = newW; - if( os_animation_timer != null ) - clearInterval(os_animation_timer.id) - os_animation_timer = new os_AnimationTimer(r,w); - os_animation_timer.id = setInterval("os_animateChangeWidth()",os_animation_delay); + if( os_animation_timer != null ) { + clearInterval( os_animation_timer.id ); + } + os_animation_timer = new os_AnimationTimer( r, w ); + os_animation_timer.id = setInterval( "os_animateChangeWidth()", os_animation_delay ); w -= fix; // this much is reserved } // trim results - if(w < 10) + if( w < 10 ) { return; - for(var i=0;i<r.resultCount;i++){ - var e = document.getElementById(r.resultText+i); + } + for( var i = 0; i < r.resultCount; i++ ) { + var e = document.getElementById( r.resultText + i ); var replace = 1; - var lastW = e.offsetWidth+1; + var lastW = e.offsetWidth + 1; var iteration = 0; var changedText = false; - while(e.offsetWidth > w && (e.offsetWidth < lastW || iteration<2)){ + while( e.offsetWidth > w && ( e.offsetWidth < lastW || iteration < 2 ) ) { changedText = true; lastW = e.offsetWidth; var l = e.innerHTML; - e.innerHTML = l.substring(0,l.length-replace)+"..."; + e.innerHTML = l.substring( 0, l.length - replace ) + '...'; iteration++; replace = 4; // how many chars to replace } - if(changedText){ + if( changedText ) { // show hint for trimmed titles - document.getElementById(r.resultTable+i).setAttribute("title",r.results[i]); + document.getElementById( r.resultTable + i ).setAttribute( 'title', r.results[i] ); } } } /** Invoked on timer to animate change in container width */ -function os_animateChangeWidth(){ +function os_animateChangeWidth() { var r = os_animation_timer.r; - var c = document.getElementById(r.container); + var c = document.getElementById( r.container ); var w = c.offsetWidth; - var normW = document.getElementById(r.searchbox).offsetWidth; - var normL = os_getElementPosition(r.searchbox).left; + var normW = document.getElementById( r.searchbox ).offsetWidth; + var normL = os_getElementPosition( r.searchbox ).left; var inc = os_animation_timer.inc; var target = os_animation_timer.target; var nw = w + inc; - if( (inc > 0 && nw >= target) || (inc <= 0 && nw <= target) ){ + if( ( inc > 0 && nw >= target ) || ( inc <= 0 && nw <= target ) ) { // finished ! - c.style.width = target+"px"; - clearInterval(os_animation_timer.id) + c.style.width = target + 'px'; + clearInterval( os_animation_timer.id ); os_animation_timer = null; - } else{ + } else { // in-progress - c.style.width = nw+"px"; - if(document.documentElement.dir == "rtl") - c.style.left = (normL + normW + (target - nw) - os_animation_timer.target - 1)+"px"; - } -} - -/** Handles data from XMLHttpRequest, and updates the suggest results */ -function os_updateResults(r, query, text, cacheKey){ - os_cache[cacheKey] = text; - r.query = query; - r.original = query; - if(text == ""){ - r.results = null; - r.resultCount = 0; - os_hideResults(r); - } else{ - try { - var p = eval('('+text+')'); // simple json parse, could do a safer one - if(p.length<2 || p[1].length == 0){ - r.results = null; - r.resultCount = 0; - os_hideResults(r); - return; - } - var c = document.getElementById(r.container); - if(c == null) - c = os_createContainer(r); - c.innerHTML = os_createResultTable(r,p[1]); - // init container table sizes - var t = document.getElementById(r.resultTable); - r.containerTotal = t.offsetHeight; - r.containerRow = t.offsetHeight / r.resultCount; - os_fitContainer(r); - os_trimResultText(r); - os_showResults(r); - } catch(e){ - // bad response from server or such - os_hideResults(r); - os_cache[cacheKey] = null; + c.style.width = nw + 'px'; + if( document.documentElement.dir == 'rtl' ) { + c.style.left = ( normL + normW + ( target - nw ) - os_animation_timer.target - 1 ) + 'px'; } } } -/** Create the result table to be placed in the container div */ -function os_createResultTable(r, results){ - var c = document.getElementById(r.container); - var width = c.offsetWidth - os_operaWidthFix(c.offsetWidth); - var html = "<table class=\"os-suggest-results\" id=\""+r.resultTable+"\" style=\"width: "+width+"px;\">"; - r.results = new Array(); - r.resultCount = results.length; - for(i=0;i<results.length;i++){ - var title = os_decodeValue(results[i]); - r.results[i] = title; - html += "<tr><td class=\"os-suggest-result\" id=\""+r.resultTable+i+"\"><span id=\""+r.resultText+i+"\">"+title+"</span></td></tr>"; +/** Change the highlighted row (i.e. suggestion), from position cur to next */ +function os_changeHighlight( r, cur, next, updateSearchBox ) { + if ( next >= r.resultCount ) { + next = r.resultCount - 1; + } + if ( next < -1 ) { + next = -1; + } + r.selected = next; + if ( cur == next ) { + return; // nothing to do. } - html+="</table>" - return html; -} -/** Fetch namespaces from checkboxes or hidden fields in the search form, - if none defined use wgSearchNamespaces global */ -function os_getNamespaces(r){ - var namespaces = ""; - var elements = document.forms[r.searchform].elements; - for(i=0; i < elements.length; i++){ - var name = elements[i].name; - if(typeof name != 'undefined' && name.length > 2 - && name[0]=='n' && name[1]=='s' - && ((elements[i].type=='checkbox' && elements[i].checked) - || (elements[i].type=='hidden' && elements[i].value=="1")) ){ - if(namespaces!="") - namespaces+="|"; - namespaces+=name.substring(2); + if( cur >= 0 ) { + var curRow = document.getElementById( r.resultTable + cur ); + if( curRow != null ) { + curRow.className = 'os-suggest-result'; } } - if(namespaces == "") - namespaces = wgSearchNamespaces.join("|"); - return namespaces; -} - -/** Update results if user hasn't already typed something else */ -function os_updateIfRelevant(r, query, text, cacheKey){ - var t = document.getElementById(r.searchbox); - if(t != null && t.value == query){ // check if response is still relevant - os_updateResults(r, query, text, cacheKey); + var newText; + if( next >= 0 ) { + var nextRow = document.getElementById( r.resultTable + next ); + if( nextRow != null ) { + nextRow.className = os_HighlightClass(); + } + newText = r.results[next]; + } else { + newText = r.original; } - r.query = query; -} - -/** Fetch results after some timeout */ -function os_delayedFetch(){ - if(os_timer == null) - return; - var r = os_timer.r; - var query = os_timer.query; - os_timer = null; - var path = wgMWSuggestTemplate.replace("{namespaces}",os_getNamespaces(r)) - .replace("{dbname}",wgDBname) - .replace("{searchTerms}",os_encodeQuery(query)); - // try to get from cache, if not fetch using ajax - var cached = os_cache[path]; - if(cached != null){ - os_updateIfRelevant(r, query, cached, path); - } else{ - var xmlhttp = sajax_init_object(); - if(xmlhttp){ - try { - xmlhttp.open("GET", path, true); - xmlhttp.onreadystatechange=function(){ - if (xmlhttp.readyState==4 && typeof os_updateIfRelevant == 'function') { - os_updateIfRelevant(r, query, xmlhttp.responseText, path); - } - }; - xmlhttp.send(null); - } catch (e) { - if (window.location.hostname == "localhost") { - alert("Your browser blocks XMLHttpRequest to 'localhost', try using a real hostname for development/testing."); - } - throw e; - } + // adjust the scrollbar if any + if( r.containerCount < r.resultCount ) { + var c = document.getElementById( r.container ); + var vStart = c.scrollTop / r.containerRow; + var vEnd = vStart + r.containerCount; + if( next < vStart ) { + c.scrollTop = next * r.containerRow; + } else if( next >= vEnd ) { + c.scrollTop = ( next - r.containerCount + 1 ) * r.containerRow; } } -} - -/** Init timed update via os_delayedUpdate() */ -function os_fetchResults(r, query, timeout){ - if(query == ""){ - os_hideResults(r); - return; - } else if(query == r.query) - return; // no change - - os_is_stopped = false; // make sure we're running - /* var cacheKey = wgDBname+":"+query; - var cached = os_cache[cacheKey]; - if(cached != null){ - os_updateResults(r,wgDBname,query,cached); - return; - } */ - - // cancel any pending fetches - if(os_timer != null && os_timer.id != null) - clearTimeout(os_timer.id); - // schedule delayed fetching of results - if(timeout != 0){ - os_timer = new os_Timer(setTimeout("os_delayedFetch()",timeout),r,query); - } else{ - os_timer = new os_Timer(null,r,query); - os_delayedFetch(); // do it now! + // update the contents of the search box + if( updateSearchBox ) { + os_updateSearchQuery( r, newText ); } - -} -/** Change the highlighted row (i.e. suggestion), from position cur to next */ -function os_changeHighlight(r, cur, next, updateSearchBox){ - if (next >= r.resultCount) - next = r.resultCount-1; - if (next < -1) - next = -1; - r.selected = next; - if (cur == next) - return; // nothing to do. - - if(cur >= 0){ - var curRow = document.getElementById(r.resultTable + cur); - if(curRow != null) - curRow.className = "os-suggest-result"; - } - var newText; - if(next >= 0){ - var nextRow = document.getElementById(r.resultTable + next); - if(nextRow != null) - nextRow.className = os_HighlightClass(); - newText = r.results[next]; - } else - newText = r.original; - - // adjust the scrollbar if any - if(r.containerCount < r.resultCount){ - var c = document.getElementById(r.container); - var vStart = c.scrollTop / r.containerRow; - var vEnd = vStart + r.containerCount; - if(next < vStart) - c.scrollTop = next * r.containerRow; - else if(next >= vEnd) - c.scrollTop = (next - r.containerCount + 1) * r.containerRow; - } - - // update the contents of the search box - if(updateSearchBox){ - os_updateSearchQuery(r,newText); - } } function os_HighlightClass() { var match = navigator.userAgent.match(/AppleWebKit\/(\d+)/); - if (match) { - var webKitVersion = parseInt(match[1]); - if (webKitVersion < 523) { + if ( match ) { + var webKitVersion = parseInt( match[1] ); + if ( webKitVersion < 523 ) { // CSS system highlight colors broken on old Safari // https://bugs.webkit.org/show_bug.cgi?id=6129 // Safari 3.0.4, 3.1 known ok - return "os-suggest-result-hl-webkit"; + return 'os-suggest-result-hl-webkit'; } } - return "os-suggest-result-hl"; + return 'os-suggest-result-hl'; } -function os_updateSearchQuery(r,newText){ - document.getElementById(r.searchbox).value = newText; - r.query = newText; -} - -/** Find event target */ -function os_getTarget(e){ - if (!e) e = window.event; - if (e.target) return e.target; - else if (e.srcElement) return e.srcElement; - else return null; +function os_updateSearchQuery( r, newText ) { + document.getElementById( r.searchbox ).value = newText; + r.query = newText; } - -/******************** - * Keyboard events - ********************/ - -/** Event handler that will fetch results on keyup */ -function os_eventKeyup(e){ - var targ = os_getTarget(e); - var r = os_map[targ.id]; - if(r == null) - return; // not our event - - // some browsers won't generate keypressed for arrow keys, catch it - if(os_keypressed_count == 0){ - os_processKey(r,os_cur_keypressed,targ); - } - var query = targ.value; - os_fetchResults(r,query,os_search_timeout); -} - -/** catch arrows up/down and escape to hide the suggestions */ -function os_processKey(r,keypressed,targ){ - if (keypressed == 40){ // Arrow Down - if (r.visible) { - os_changeHighlight(r, r.selected, r.selected+1, true); - } else if(os_timer == null){ - // user wants to get suggestions now - r.query = ""; - os_fetchResults(r,targ.value,0); - } - } else if (keypressed == 38){ // Arrow Up - if (r.visible){ - os_changeHighlight(r, r.selected, r.selected-1, true); - } - } else if(keypressed == 27){ // Escape - document.getElementById(r.searchbox).value = r.original; - r.query = r.original; - os_hideResults(r); - } else if(r.query != document.getElementById(r.searchbox).value){ - // os_hideResults(r); // don't show old suggestions - } -} - -/** When keys is held down use a timer to output regular events */ -function os_eventKeypress(e){ - var targ = os_getTarget(e); - var r = os_map[targ.id]; - if(r == null) - return; // not our event - - var keypressed = os_cur_keypressed; - if(keypressed == 38 || keypressed == 40){ - var d = new Date() - var now = d.getTime(); - if(now - os_last_keypress < 120){ - os_last_keypress = now; - return; - } - } - - os_keypressed_count++; - os_processKey(r,keypressed,targ); -} - -/** Catch the key code (Firefox bug) */ -function os_eventKeydown(e){ - if (!e) e = window.event; - var targ = os_getTarget(e); - var r = os_map[targ.id]; - if(r == null) - return; // not our event - - os_mouse_moved = false; - - os_cur_keypressed = (e.keyCode == undefined) ? e.which : e.keyCode; - os_last_keypress = 0; - os_keypressed_count = 0; -} - -/** Event: loss of focus of input box */ -function os_eventBlur(e){ - var targ = os_getTarget(e); - var r = os_map[targ.id]; - if(r == null) - return; // not our event - if(!os_mouse_pressed) - os_hideResults(r); -} - -/** Event: focus (catch only when stopped) */ -function os_eventFocus(e){ - // nothing happens here? -} - - - /******************** * Mouse events ********************/ /** Mouse over the container */ -function os_eventMouseover(srcId, e){ - var targ = os_getTarget(e); +function os_eventMouseover( srcId, e ) { + var targ = os_getTarget( e ); var r = os_map[srcId]; - if(r == null || !os_mouse_moved) + if( r == null || !os_mouse_moved ) { return; // not our event - var num = os_getNumberSuffix(targ.id); - if(num >= 0) - os_changeHighlight(r,r.selected,num,false); - + } + var num = os_getNumberSuffix( targ.id ); + if( num >= 0 ) { + os_changeHighlight( r, r.selected, num, false ); + } } /* Get row where the event occured (from its id) */ -function os_getNumberSuffix(id){ - var num = id.substring(id.length-2); - if( ! (num.charAt(0) >= '0' && num.charAt(0) <= '9') ) - num = num.substring(1); - if(os_isNumber(num)) - return parseInt(num); - else +function os_getNumberSuffix( id ) { + var num = id.substring( id.length - 2 ); + if( !( num.charAt( 0 ) >= '0' && num.charAt( 0 ) <= '9' ) ) { + num = num.substring( 1 ); + } + if( os_isNumber( num ) ) { + return parseInt( num ); + } else { return -1; + } } /** Save mouse move as last action */ -function os_eventMousemove(srcId, e){ +function os_eventMousemove( srcId, e ) { os_mouse_moved = true; } -/** Mouse button held down, register possible click */ -function os_eventMousedown(srcId, e){ - var targ = os_getTarget(e); +/** Mouse button held down, register possible click */ +function os_eventMousedown( srcId, e ) { + var targ = os_getTarget( e ); var r = os_map[srcId]; - if(r == null) + if( r == null ) { return; // not our event - var num = os_getNumberSuffix(targ.id); + } + var num = os_getNumberSuffix( targ.id ); os_mouse_pressed = true; - if(num >= 0){ + if( num >= 0 ) { os_mouse_num = num; - // os_updateSearchQuery(r,r.results[num]); + // os_updateSearchQuery( r, r.results[num] ); } // keep the focus on the search field - document.getElementById(r.searchbox).focus(); + document.getElementById( r.searchbox ).focus(); return false; // prevents selection } /** Mouse button released, check for click on some row */ -function os_eventMouseup(srcId, e){ - var targ = os_getTarget(e); +function os_eventMouseup( srcId, e ) { + var targ = os_getTarget( e ); var r = os_map[srcId]; - if(r == null) + if( r == null ) { return; // not our event - var num = os_getNumberSuffix(targ.id); + } + var num = os_getNumberSuffix( targ.id ); - if(num >= 0 && os_mouse_num == num){ - os_updateSearchQuery(r,r.results[num]); - os_hideResults(r); - document.getElementById(r.searchform).submit(); + if( num >= 0 && os_mouse_num == num ) { + os_updateSearchQuery( r, r.results[num] ); + os_hideResults( r ); + document.getElementById( r.searchform ).submit(); } os_mouse_pressed = false; // keep the focus on the search field - document.getElementById(r.searchbox).focus(); + document.getElementById( r.searchbox ).focus(); } -/** Check if x is a valid integer */ -function os_isNumber(x){ - if(x == "" || isNaN(x)) - return false; - for(var i=0;i<x.length;i++){ - var c = x.charAt(i); - if( ! (c >= '0' && c <= '9') ) - return false; - } - return true; -} - - -/** When the form is submitted hide everything, cancel updates... */ -function os_eventOnsubmit(e){ - var targ = os_getTarget(e); - - os_is_stopped = true; - // kill timed requests - if(os_timer != null && os_timer.id != null){ - clearTimeout(os_timer.id); - os_timer = null; - } - // Hide all suggestions - for(i=0;i<os_autoload_inputs.length;i++){ - var r = os_map[os_autoload_inputs[i]]; - if(r != null){ - var b = document.getElementById(r.searchform); - if(b != null && b == targ){ - // set query value so the handler won't try to fetch additional results - r.query = document.getElementById(r.searchbox).value; - } - os_hideResults(r); - } - } - return true; -} - -function os_hookEvent(element, hookName, hookFunct) { - if (element.addEventListener) { - element.addEventListener(hookName, hookFunct, false); - } else if (window.attachEvent) { - element.attachEvent("on" + hookName, hookFunct); - } -} - -/** Init Result objects and event handlers */ -function os_initHandlers(name, formname, element){ - var r = new os_Results(name, formname); - // event handler - os_hookEvent(element, "keyup", function(event) { os_eventKeyup(event); }); - os_hookEvent(element, "keydown", function(event) { os_eventKeydown(event); }); - os_hookEvent(element, "keypress", function(event) { os_eventKeypress(event); }); - os_hookEvent(element, "blur", function(event) { os_eventBlur(event); }); - os_hookEvent(element, "focus", function(event) { os_eventFocus(event); }); - element.setAttribute("autocomplete","off"); - // stopping handler - os_hookEvent(document.getElementById(formname), "submit", function(event){ return os_eventOnsubmit(event); }); - os_map[name] = r; - // toggle link - if(document.getElementById(r.toggle) == null){ - // TODO: disable this while we figure out a way for this to work in all browsers - /* if(name=='searchInput'){ - // special case: place above the main search box - var t = os_createToggle(r,"os-suggest-toggle"); - var searchBody = document.getElementById('searchBody'); - var first = searchBody.parentNode.firstChild.nextSibling.appendChild(t); - } else{ - // default: place below search box to the right - var t = os_createToggle(r,"os-suggest-toggle-def"); - var top = element.offsetTop + element.offsetHeight; - var left = element.offsetLeft + element.offsetWidth; - t.style.position = "absolute"; - t.style.top = top + "px"; - t.style.left = left + "px"; - element.parentNode.appendChild(t); - // only now width gets calculated, shift right - left -= t.offsetWidth; - t.style.left = left + "px"; - t.style.visibility = "visible"; - } */ - } - -} +/** Toggle stuff seems to be dead code? */ /** Return the span element that contains the toggle link */ -function os_createToggle(r,className){ - var t = document.createElement("span"); +function os_createToggle( r, className ) { + var t = document.createElement( 'span' ); t.className = className; - t.setAttribute("id", r.toggle); - var link = document.createElement("a"); - link.setAttribute("href","javascript:void(0);"); - link.onclick = function(){ os_toggle(r.searchbox,r.searchform) }; - var msg = document.createTextNode(wgMWSuggestMessages[0]); - link.appendChild(msg); - t.appendChild(link); + t.setAttribute( 'id', r.toggle ); + var link = document.createElement( 'a' ); + link.setAttribute( 'href', 'javascript:void(0);' ); + link.onclick = function() { os_toggle( r.searchbox, r.searchform ); }; + var msg = document.createTextNode( wgMWSuggestMessages[0] ); + link.appendChild( msg ); + t.appendChild( link ); return t; } /** Call when user clicks on some of the toggle links */ -function os_toggle(inputId,formName){ +function os_toggle( inputId, formName ) { r = os_map[inputId]; var msg = ''; - if(r == null){ - os_enableSuggestionsOn(inputId,formName); + if( r == null ) { + os_enableSuggestionsOn( inputId, formName ); r = os_map[inputId]; msg = wgMWSuggestMessages[0]; } else{ - os_disableSuggestionsOn(inputId,formName); + os_disableSuggestionsOn( inputId, formName ); msg = wgMWSuggestMessages[1]; } // change message - var link = document.getElementById(r.toggle).firstChild; - link.replaceChild(document.createTextNode(msg),link.firstChild); -} - -/** Call this to enable suggestions on input (id=inputId), on a form (name=formName) */ -function os_enableSuggestionsOn(inputId, formName){ - os_initHandlers( inputId, formName, document.getElementById(inputId) ); -} - -/** Call this to disable suggestios on input box (id=inputId) */ -function os_disableSuggestionsOn(inputId){ - r = os_map[inputId]; - if(r != null){ - // cancel/hide results - os_timer = null; - os_hideResults(r); - // turn autocomplete on ! - document.getElementById(inputId).setAttribute("autocomplete","on"); - // remove descriptor - os_map[inputId] = null; - } -} - -/** Initialization, call upon page onload */ -function os_MWSuggestInit() { - for(i=0;i<os_autoload_inputs.length;i++){ - var id = os_autoload_inputs[i]; - var form = os_autoload_forms[i]; - element = document.getElementById( id ); - if(element != null) - os_initHandlers(id,form,element); - } + var link = document.getElementById( r.toggle ).firstChild; + link.replaceChild( document.createTextNode( msg ), link.firstChild ); } -hookEvent("load", os_MWSuggestInit); +hookEvent( 'load', os_MWSuggestInit ); |