2 * OpenSearch ajax suggestion engine for MediaWiki
4 * uses core MediaWiki open search support to fetch suggestions
5 * and show them below search boxes and other inputs
7 * by Robert Stojnic (April 2008)
10 // search_box_id -> Results object
12 // cached data, url -> json_text
14 // global variables for suggest_keypress
15 var os_cur_keypressed
= 0;
16 var os_keypressed_count
= 0;
19 // tie mousedown/up events
20 var os_mouse_pressed
= false;
21 var os_mouse_num
= -1;
22 // if true, the last change was made by mouse (and not keyboard)
23 var os_mouse_moved
= false;
24 // delay between keypress and suggestion (in ms)
25 var os_search_timeout
= 250;
26 // these pairs of inputs/forms will be autoloaded at startup
27 var os_autoload_inputs
= new Array('searchInput', 'searchInput2', 'powerSearchText', 'searchText');
28 var os_autoload_forms
= new Array('searchform', 'searchform2', 'powersearch', 'search' );
29 // if we stopped the service
30 var os_is_stopped
= false;
31 // max lines to show in suggest table
32 var os_max_lines_per_suggest
= 7;
33 // number of steps to animate expansion/contraction of container width
34 var os_animation_steps
= 6;
35 // num of pixels of smallest step
36 var os_animation_min_step
= 2;
37 // delay between steps (in ms)
38 var os_animation_delay
= 30;
39 // max width of container in percent of normal size (1 == 100%)
40 var os_container_max_width
= 2;
41 // currently active animation timer
42 var os_animation_timer
= null;
44 /** Timeout timer class that will fetch the results */
45 function os_Timer( id
, r
, query
) {
51 /** Property class for single search box */
52 function os_Results( name
, formname
) {
53 this.searchform
= formname
; // id of the searchform
54 this.searchbox
= name
; // id of the searchbox
55 this.container
= name
+ 'Suggest'; // div that holds results
56 this.resultTable
= name
+ 'Result'; // id base for the result table (+num = table row)
57 this.resultText
= name
+ 'ResultText'; // id base for the spans within result tables (+num)
58 this.toggle
= name
+ 'Toggle'; // div that has the toggle (enable/disable) link
59 this.query
= null; // last processed query
60 this.results
= null; // parsed titles
61 this.resultCount
= 0; // number of results
62 this.original
= null; // query that user entered
63 this.selected
= -1; // which result is selected
64 this.containerCount
= 0; // number of results visible in container
65 this.containerRow
= 0; // height of result field in the container
66 this.containerTotal
= 0; // total height of the container will all results
67 this.visible
= false; // if container is visible
68 this.stayHidden
= false; // don't try to show if lost focus
71 /** Timer user to animate expansion/contraction of container width */
72 function os_AnimationTimer( r, target ) {
74 var current = document.getElementById(r.container).offsetWidth;
75 this.inc = Math.round( ( target - current ) / os_animation_steps );
76 if( this.inc < os_animation_min_step && this.inc >=0 ) {
77 this.inc = os_animation_min_step; // minimal animation step
79 if( this.inc > -os_animation_min_step && this.inc < 0 ) {
80 this.inc = -os_animation_min_step;
89 /** Initialization, call upon page onload */
90 function os_MWSuggestInit() {
91 for( i
= 0; i
< os_autoload_inputs
.length
; i
++ ) {
92 var id
= os_autoload_inputs
[i
];
93 var form
= os_autoload_forms
[i
];
94 element
= document
.getElementById( id
);
95 if( element
!== null ) {
96 os_initHandlers( id
, form
, element
);
101 /** Init Result objects and event handlers */
102 function os_initHandlers( name
, formname
, element
) {
103 var r
= new os_Results( name
, formname
);
105 os_hookEvent( element
, 'keyup', function( event
) { os_eventKeyup( event
); } );
106 os_hookEvent( element
, 'keydown', function( event
) { os_eventKeydown( event
); } );
107 os_hookEvent( element
, 'keypress', function( event
) { os_eventKeypress( event
); } );
108 os_hookEvent( element
, 'blur', function( event
) { os_eventBlur( event
); } );
109 os_hookEvent( element
, 'focus', function( event
) { os_eventFocus( event
); } );
110 element
.setAttribute( 'autocomplete', 'off' );
112 os_hookEvent( document
.getElementById( formname
), 'submit', function( event
) { return os_eventOnsubmit( event
); } );
115 if( document
.getElementById( r
.toggle
) === null ) {
116 // TODO: disable this while we figure out a way for this to work in all browsers
117 /* if( name == 'searchInput' ) {
118 // special case: place above the main search box
119 var t = os_createToggle( r, 'os-suggest-toggle' );
120 var searchBody = document.getElementById( 'searchBody' );
121 var first = searchBody.parentNode.firstChild.nextSibling.appendChild(t);
123 // default: place below search box to the right
124 var t = os_createToggle( r, 'os-suggest-toggle-def' );
125 var top = element.offsetTop + element.offsetHeight;
126 var left = element.offsetLeft + element.offsetWidth;
127 t.style.position = 'absolute';
128 t.style.top = top + 'px';
129 t.style.left = left + 'px';
130 element.parentNode.appendChild( t );
131 // only now width gets calculated, shift right
132 left -= t.offsetWidth;
133 t.style.left = left + 'px';
134 t.style.visibility = 'visible';
140 function os_hookEvent( element
, hookName
, hookFunct
) {
141 if ( element
.addEventListener
) {
142 element
.addEventListener( hookName
, hookFunct
, false );
143 } else if ( window
.attachEvent
) {
144 element
.attachEvent( 'on' + hookName
, hookFunct
);
148 /********************
150 ********************/
152 /** Event handler that will fetch results on keyup */
153 function os_eventKeyup( e
) {
154 var targ
= os_getTarget( e
);
155 var r
= os_map
[targ
.id
];
157 return; // not our event
160 // some browsers won't generate keypressed for arrow keys, catch it
161 if( os_keypressed_count
=== 0 ) {
162 os_processKey( r
, os_cur_keypressed
, targ
);
164 var query
= targ
.value
;
165 os_fetchResults( r
, query
, os_search_timeout
);
168 /** catch arrows up/down and escape to hide the suggestions */
169 function os_processKey( r, keypressed, targ ) {
170 if ( keypressed == 40 ) { // Arrow Down
172 os_changeHighlight( r, r.selected, r.selected + 1, true );
173 } else if( os_timer === null ) {
174 // user wants to get suggestions now
176 os_fetchResults( r, targ.value, 0 );
178 } else if ( keypressed == 38 ) { // Arrow Up
180 os_changeHighlight( r, r.selected, r.selected - 1, true );
182 } else if( keypressed == 27 ) { // Escape
183 document.getElementById( r.searchbox ).value = r.original;
184 r.query = r.original;
186 } else if( r.query != document.getElementById( r.searchbox ).value ) {
187 // os_hideResults( r ); // don't show old suggestions
191 /** When keys is held down use a timer to output regular events */
192 function os_eventKeypress( e
) {
193 var targ
= os_getTarget( e
);
194 var r
= os_map
[targ
.id
];
196 return; // not our event
199 var keypressed
= os_cur_keypressed
;
201 os_keypressed_count
++;
202 os_processKey( r
, keypressed
, targ
);
205 /** Catch the key code (Firefox bug) */
206 function os_eventKeydown( e
) {
210 var targ
= os_getTarget( e
);
211 var r
= os_map
[targ
.id
];
213 return; // not our event
216 os_mouse_moved
= false;
218 os_cur_keypressed
= ( e
.keyCode
=== undefined ) ? e
.which
: e
.keyCode
;
219 os_keypressed_count
= 0;
222 /** Event: loss of focus of input box */
223 function os_eventBlur( e
) {
224 var targ
= os_getTarget( e
);
225 var r
= os_map
[targ
.id
];
227 return; // not our event
229 if( !os_mouse_pressed
) {
231 // force canvas to stay hidden
233 // cancel any pending fetches
234 if( os_timer
!== null && os_timer
.id
!== null ) {
235 clearTimeout( os_timer
.id
);
241 /** Event: focus (catch only when stopped) */
242 function os_eventFocus( e
) {
243 var targ
= os_getTarget( e
);
244 var r
= os_map
[targ
.id
];
246 return; // not our event
248 r
.stayHidden
= false;
252 /** When the form is submitted hide everything, cancel updates... */
253 function os_eventOnsubmit( e
) {
254 var targ
= os_getTarget( e
);
256 os_is_stopped
= true;
257 // kill timed requests
258 if( os_timer
!== null && os_timer
.id
!== null ) {
259 clearTimeout( os_timer
.id
);
262 // Hide all suggestions
263 for( i
= 0; i
< os_autoload_inputs
.length
; i
++ ) {
264 var r
= os_map
[os_autoload_inputs
[i
]];
266 var b
= document
.getElementById( r
.searchform
);
267 if( b
!== null && b
== targ
) {
268 // set query value so the handler won't try to fetch additional results
269 r
.query
= document
.getElementById( r
.searchbox
).value
;
279 /** Hide results div */
280 function os_hideResults( r
) {
281 var c
= document
.getElementById( r
.container
);
283 c
.style
.visibility
= 'hidden';
289 /** Show results div */
290 function os_showResults( r
) {
291 if( os_is_stopped
) {
297 os_fitContainer( r
);
298 var c
= document
.getElementById( r
.container
);
302 c
.style
.visibility
= 'visible';
307 function os_operaWidthFix( x
) {
308 // For browsers that don't understand overflow-x, estimate scrollbar width
309 if( typeof document
.body
.style
.overflowX
!= 'string' ) {
315 function os_encodeQuery( value
) {
316 if ( encodeURIComponent
) {
317 return encodeURIComponent( value
);
320 return escape( value
);
325 function os_decodeValue( value
) {
326 if ( decodeURIComponent
) {
327 return decodeURIComponent( value
);
330 return unescape( value
);
335 /** Brower-dependent functions to find window inner size, and scroll status */
336 function f_clientWidth() {
337 return f_filterResults(
338 window
.innerWidth
? window
.innerWidth
: 0,
339 document
.documentElement
? document
.documentElement
.clientWidth
: 0,
340 document
.body
? document
.body
.clientWidth
: 0
344 function f_clientHeight() {
345 return f_filterResults(
346 window
.innerHeight
? window
.innerHeight
: 0,
347 document
.documentElement
? document
.documentElement
.clientHeight
: 0,
348 document
.body
? document
.body
.clientHeight
: 0
352 function f_scrollLeft() {
353 return f_filterResults(
354 window
.pageXOffset
? window
.pageXOffset
: 0,
355 document
.documentElement
? document
.documentElement
.scrollLeft
: 0,
356 document
.body
? document
.body
.scrollLeft
: 0
360 function f_scrollTop() {
361 return f_filterResults(
362 window
.pageYOffset
? window
.pageYOffset
: 0,
363 document
.documentElement
? document
.documentElement
.scrollTop
: 0,
364 document
.body
? document
.body
.scrollTop
: 0
368 function f_filterResults( n_win
, n_docel
, n_body
) {
369 var n_result
= n_win
? n_win
: 0;
370 if ( n_docel
&& ( !n_result
|| ( n_result
> n_docel
) ) ) {
373 return n_body
&& ( !n_result
|| ( n_result
> n_body
) ) ? n_body
: n_result
;
376 /** Get the height available for the results container */
377 function os_availableHeight( r
) {
378 var absTop
= document
.getElementById( r
.container
).style
.top
;
379 var px
= absTop
.lastIndexOf( 'px' );
381 absTop
= absTop
.substring( 0, px
);
383 return f_clientHeight() - ( absTop
- f_scrollTop() );
386 /** Get element absolute position {left,top} */
387 function os_getElementPosition( elemID
) {
388 var offsetTrail
= document
.getElementById( elemID
);
391 while ( offsetTrail
) {
392 offsetLeft
+= offsetTrail
.offsetLeft
;
393 offsetTop
+= offsetTrail
.offsetTop
;
394 offsetTrail
= offsetTrail
.offsetParent
;
396 if ( navigator
.userAgent
.indexOf('Mac') != -1 && typeof document
.body
.leftMargin
!= 'undefined' ) {
397 offsetLeft
+= document
.body
.leftMargin
;
398 offsetTop
+= document
.body
.topMargin
;
400 return { left
:offsetLeft
, top
:offsetTop
};
403 /** Create the container div that will hold the suggested titles */
404 function os_createContainer( r
) {
405 var c
= document
.createElement( 'div' );
406 var s
= document
.getElementById( r
.searchbox
);
407 var pos
= os_getElementPosition( r
.searchbox
);
409 var top
= pos
.top
+ s
.offsetHeight
;
410 c
.className
= 'os-suggest';
411 c
.setAttribute( 'id', r
.container
);
412 document
.body
.appendChild( c
);
414 // dynamically generated style params
415 // IE workaround, cannot explicitely set "style" attribute
416 c
= document
.getElementById( r
.container
);
417 c
.style
.top
= top
+ 'px';
418 c
.style
.left
= left
+ 'px';
419 c
.style
.width
= s
.offsetWidth
+ 'px';
421 // mouse event handlers
422 c
.onmouseover = function( event
) { os_eventMouseover( r
.searchbox
, event
); };
423 c
.onmousemove = function( event
) { os_eventMousemove( r
.searchbox
, event
); };
424 c
.onmousedown = function( event
) { return os_eventMousedown( r
.searchbox
, event
); };
425 c
.onmouseup = function( event
) { os_eventMouseup( r
.searchbox
, event
); };
429 /** change container height to fit to screen */
430 function os_fitContainer( r
) {
431 var c
= document
.getElementById( r
.container
);
432 var h
= os_availableHeight( r
) - 20;
433 var inc
= r
.containerRow
;
434 h
= parseInt( h
/ inc
) * inc
;
435 if( h
< ( 2 * inc
) && r
.resultCount
> 1 ) { // min: two results
438 if( ( h
/ inc
) > os_max_lines_per_suggest
) {
439 h
= inc
* os_max_lines_per_suggest
;
441 if( h
< r
.containerTotal
) {
442 c
.style
.height
= h
+ 'px';
443 r
.containerCount
= parseInt( Math
.round( h
/ inc
) );
445 c
.style
.height
= r
.containerTotal
+ 'px';
446 r
.containerCount
= r
.resultCount
;
450 /** If some entries are longer than the box, replace text with "..." */
451 function os_trimResultText( r
) {
452 // find max width, first see if we could expand the container to fit it
454 for( var i
= 0; i
< r
.resultCount
; i
++ ) {
455 var e
= document
.getElementById( r
.resultText
+ i
);
456 if( e
.offsetWidth
> maxW
) {
457 maxW
= e
.offsetWidth
;
460 var w
= document
.getElementById( r
.container
).offsetWidth
;
462 if( r
.containerCount
< r
.resultCount
) {
463 fix
= 20; // give 20px for scrollbar
465 fix
= os_operaWidthFix( w
);
468 fix
= 4; // basic padding
472 // resize container to fit more data if permitted
473 var normW
= document
.getElementById( r
.searchbox
).offsetWidth
;
474 var prop
= maxW
/ normW
;
475 if( prop
> os_container_max_width
) {
476 prop
= os_container_max_width
;
477 } else if( prop
< 1 ) {
480 var newW
= Math
.round( normW
* prop
);
483 if( os_animation_timer
!== null ) {
484 clearInterval( os_animation_timer
.id
);
486 os_animation_timer
= new os_AnimationTimer( r
, w
);
487 os_animation_timer
.id
= setInterval( "os_animateChangeWidth()", os_animation_delay
);
488 w
-= fix
; // this much is reserved
495 for( var i
= 0; i
< r
.resultCount
; i
++ ) {
496 var e
= document
.getElementById( r
.resultText
+ i
);
498 var lastW
= e
.offsetWidth
+ 1;
500 var changedText
= false;
501 while( e
.offsetWidth
> w
&& ( e
.offsetWidth
< lastW
|| iteration
< 2 ) ) {
503 lastW
= e
.offsetWidth
;
505 e
.innerHTML
= l
.substring( 0, l
.length
- replace
) + '...';
507 replace
= 4; // how many chars to replace
510 // show hint for trimmed titles
511 document
.getElementById( r
.resultTable
+ i
).setAttribute( 'title', r
.results
[i
] );
516 /** Invoked on timer to animate change in container width */
517 function os_animateChangeWidth() {
518 var r
= os_animation_timer
.r
;
519 var c
= document
.getElementById( r
.container
);
520 var w
= c
.offsetWidth
;
521 var normW
= document
.getElementById( r
.searchbox
).offsetWidth
;
522 var normL
= os_getElementPosition( r
.searchbox
).left
;
523 var inc
= os_animation_timer
.inc
;
524 var target
= os_animation_timer
.target
;
526 if( ( inc
> 0 && nw
>= target
) || ( inc
<= 0 && nw
<= target
) ) {
528 c
.style
.width
= target
+ 'px';
529 clearInterval( os_animation_timer
.id
);
530 os_animation_timer
= null;
533 c
.style
.width
= nw
+ 'px';
534 if( document
.documentElement
.dir
== 'rtl' ) {
535 c
.style
.left
= ( normL
+ normW
+ ( target
- nw
) - os_animation_timer
.target
- 1 ) + 'px';
540 /** Handles data from XMLHttpRequest, and updates the suggest results */
541 function os_updateResults( r
, query
, text
, cacheKey
) {
542 os_cache
[cacheKey
] = text
;
551 var p
= eval( '(' + text
+ ')' ); // simple json parse, could do a safer one
552 if( p
.length
< 2 || p
[1].length
=== 0 ) {
558 var c
= document
.getElementById( r
.container
);
560 c
= os_createContainer( r
);
562 c
.innerHTML
= os_createResultTable( r
, p
[1] );
563 // init container table sizes
564 var t
= document
.getElementById( r
.resultTable
);
565 r
.containerTotal
= t
.offsetHeight
;
566 r
.containerRow
= t
.offsetHeight
/ r
.resultCount
;
567 os_fitContainer( r
);
568 os_trimResultText( r
);
571 // bad response from server or such
573 os_cache
[cacheKey
] = null;
578 /** Create the result table to be placed in the container div */
579 function os_createResultTable( r
, results
) {
580 var c
= document
.getElementById( r
.container
);
581 var width
= c
.offsetWidth
- os_operaWidthFix( c
.offsetWidth
);
582 var html
= '<table class="os-suggest-results" id="' + r
.resultTable
+ '" style="width: ' + width
+ 'px;">';
583 r
.results
= new Array();
584 r
.resultCount
= results
.length
;
585 for( i
= 0; i
< results
.length
; i
++ ) {
586 var title
= os_decodeValue( results
[i
] );
587 r
.results
[i
] = title
;
588 html
+= '<tr><td class="os-suggest-result" id="' + r
.resultTable
+ i
+ '"><span id="' + r
.resultText
+ i
+ '">' + title
+ '</span></td></tr>';
594 /** Fetch namespaces from checkboxes or hidden fields in the search form,
595 if none defined use wgSearchNamespaces global */
596 function os_getNamespaces( r
) {
598 var elements
= document
.forms
[r
.searchform
].elements
;
599 for( i
= 0; i
< elements
.length
; i
++ ) {
600 var name
= elements
[i
].name
;
601 if( typeof name
!= 'undefined' && name
.length
> 2 && name
[0] == 'n' &&
603 ( elements
[i
].type
== 'checkbox' && elements
[i
].checked
) ||
604 ( elements
[i
].type
== 'hidden' && elements
[i
].value
== '1' )
607 if( namespaces
!= '' ) {
610 namespaces
+= name
.substring( 2 );
613 if( namespaces
== '' ) {
614 namespaces
= wgSearchNamespaces
.join('|');
619 /** Update results if user hasn't already typed something else */
620 function os_updateIfRelevant( r
, query
, text
, cacheKey
) {
621 var t
= document
.getElementById( r
.searchbox
);
622 if( t
!== null && t
.value
== query
) { // check if response is still relevant
623 os_updateResults( r
, query
, text
, cacheKey
);
628 /** Fetch results after some timeout */
629 function os_delayedFetch() {
630 if( os_timer
=== null ) {
634 var query
= os_timer
.query
;
636 var path
= wgMWSuggestTemplate
.replace( "{namespaces}", os_getNamespaces( r
) )
637 .replace( "{dbname}", wgDBname
)
638 .replace( "{searchTerms}", os_encodeQuery( query
) );
640 // try to get from cache, if not fetch using ajax
641 var cached
= os_cache
[path
];
642 if( cached
!== null ) {
643 os_updateIfRelevant( r
, query
, cached
, path
);
645 var xmlhttp
= sajax_init_object();
648 xmlhttp
.open( 'GET', path
, true );
649 xmlhttp
.onreadystatechange = function() {
650 if ( xmlhttp
.readyState
== 4 && typeof os_updateIfRelevant
== 'function' ) {
651 os_updateIfRelevant( r
, query
, xmlhttp
.responseText
, path
);
654 xmlhttp
.send( null );
656 if ( window
.location
.hostname
== 'localhost' ) {
657 alert( "Your browser blocks XMLHttpRequest to 'localhost', try using a real hostname for development/testing." );
665 /** Init timed update via os_delayedUpdate() */
666 function os_fetchResults( r
, query
, timeout
) {
671 } else if( query
== r
.query
) {
675 os_is_stopped
= false; // make sure we're running
677 /* var cacheKey = wgDBname + ':' + query;
678 var cached = os_cache[cacheKey];
679 if( cached != null ) {
680 os_updateResults( r, wgDBname, query, cached );
684 // cancel any pending fetches
685 if( os_timer
!== null && os_timer
.id
!== null ) {
686 clearTimeout( os_timer
.id
);
688 // schedule delayed fetching of results
689 if( timeout
!== 0 ) {
690 os_timer
= new os_Timer( setTimeout( "os_delayedFetch()", timeout
), r
, query
);
692 os_timer
= new os_Timer( null, r
, query
);
693 os_delayedFetch(); // do it now!
698 /** Change the highlighted row (i.e. suggestion), from position cur to next */
699 function os_changeHighlight( r
, cur
, next
, updateSearchBox
) {
700 if ( next
>= r
.resultCount
) {
701 next
= r
.resultCount
- 1;
708 return; // nothing to do.
712 var curRow
= document
.getElementById( r
.resultTable
+ cur
);
713 if( curRow
!== null ) {
714 curRow
.className
= 'os-suggest-result';
719 var nextRow
= document
.getElementById( r
.resultTable
+ next
);
720 if( nextRow
!== null ) {
721 nextRow
.className
= os_HighlightClass();
723 newText
= r
.results
[next
];
725 newText
= r
.original
;
728 // adjust the scrollbar if any
729 if( r
.containerCount
< r
.resultCount
) {
730 var c
= document
.getElementById( r
.container
);
731 var vStart
= c
.scrollTop
/ r
.containerRow
;
732 var vEnd
= vStart
+ r
.containerCount
;
733 if( next
< vStart
) {
734 c
.scrollTop
= next
* r
.containerRow
;
735 } else if( next
>= vEnd
) {
736 c
.scrollTop
= ( next
- r
.containerCount
+ 1 ) * r
.containerRow
;
740 // update the contents of the search box
741 if( updateSearchBox
) {
742 os_updateSearchQuery( r
, newText
);
746 function os_HighlightClass() {
747 var match
= navigator
.userAgent
.match(/AppleWebKit\/(\d+)/);
749 var webKitVersion
= parseInt( match
[1] );
750 if ( webKitVersion
< 523 ) {
751 // CSS system highlight colors broken on old Safari
752 // https://bugs.webkit.org/show_bug.cgi?id=6129
753 // Safari 3.0.4, 3.1 known ok
754 return 'os-suggest-result-hl-webkit';
757 return 'os-suggest-result-hl';
760 function os_updateSearchQuery( r
, newText
) {
761 document
.getElementById( r
.searchbox
).value
= newText
;
765 /** Find event target */
766 function os_getTarget( e
) {
772 } else if ( e
.srcElement
) {
780 /********************
782 ********************/
784 /** Mouse over the container */
785 function os_eventMouseover( srcId
, e
) {
786 var targ
= os_getTarget( e
);
787 var r
= os_map
[srcId
];
788 if( r
=== null || !os_mouse_moved
) {
789 return; // not our event
791 var num
= os_getNumberSuffix( targ
.id
);
793 os_changeHighlight( r
, r
.selected
, num
, false );
797 /* Get row where the event occured (from its id) */
798 function os_getNumberSuffix( id
) {
799 var num
= id
.substring( id
.length
- 2 );
800 if( !( num
.charAt( 0 ) >= '0' && num
.charAt( 0 ) <= '9' ) ) {
801 num
= num
.substring( 1 );
803 if( os_isNumber( num
) ) {
804 return parseInt( num
);
810 /** Save mouse move as last action */
811 function os_eventMousemove( srcId
, e
) {
812 os_mouse_moved
= true;
815 /** Mouse button held down, register possible click */
816 function os_eventMousedown( srcId
, e
) {
817 var targ
= os_getTarget( e
);
818 var r
= os_map
[srcId
];
820 return; // not our event
822 var num
= os_getNumberSuffix( targ
.id
);
824 os_mouse_pressed
= true;
827 // os_updateSearchQuery( r, r.results[num] );
829 // keep the focus on the search field
830 document
.getElementById( r
.searchbox
).focus();
832 return false; // prevents selection
835 /** Mouse button released, check for click on some row */
836 function os_eventMouseup( srcId
, e
) {
837 var targ
= os_getTarget( e
);
838 var r
= os_map
[srcId
];
840 return; // not our event
842 var num
= os_getNumberSuffix( targ
.id
);
844 if( num
>= 0 && os_mouse_num
== num
) {
845 os_updateSearchQuery( r
, r
.results
[num
] );
847 document
.getElementById( r
.searchform
).submit();
849 os_mouse_pressed
= false;
850 // keep the focus on the search field
851 document
.getElementById( r
.searchbox
).focus();
854 /** Check if x is a valid integer */
855 function os_isNumber( x
) {
856 if( x
== '' || isNaN( x
) ) {
859 for( var i
= 0; i
< x
.length
; i
++ ) {
860 var c
= x
.charAt( i
);
861 if( !( c
>= '0' && c
<= '9' ) ) {
868 /** Return the span element that contains the toggle link */
869 function os_createToggle( r
, className
) {
870 var t
= document
.createElement( 'span' );
871 t
.className
= className
;
872 t
.setAttribute( 'id', r
.toggle
);
873 var link
= document
.createElement( 'a' );
874 link
.setAttribute( 'href', 'javascript:void(0);' );
875 link
.onclick = function() { os_toggle( r
.searchbox
, r
.searchform
); };
876 var msg
= document
.createTextNode( wgMWSuggestMessages
[0] );
877 link
.appendChild( msg
);
878 t
.appendChild( link
);
882 /** Call when user clicks on some of the toggle links */
883 function os_toggle( inputId
, formName
) {
887 os_enableSuggestionsOn( inputId
, formName
);
889 msg
= wgMWSuggestMessages
[0];
891 os_disableSuggestionsOn( inputId
, formName
);
892 msg
= wgMWSuggestMessages
[1];
895 var link
= document
.getElementById( r
.toggle
).firstChild
;
896 link
.replaceChild( document
.createTextNode( msg
), link
.firstChild
);
899 /** Call this to enable suggestions on input (id=inputId), on a form (name=formName) */
900 function os_enableSuggestionsOn( inputId
, formName
) {
901 os_initHandlers( inputId
, formName
, document
.getElementById( inputId
) );
904 /** Call this to disable suggestios on input box (id=inputId) */
905 function os_disableSuggestionsOn( inputId
) {
908 // cancel/hide results
911 // turn autocomplete on !
912 document
.getElementById( inputId
).setAttribute( 'autocomplete', 'on' );
914 os_map
[inputId
] = null;
917 // Remove the element from the os_autoload_* arrays
918 var index
= os_autoload_inputs
.indexOf( inputId
);
920 os_autoload_inputs
[index
] = os_autoload_forms
[index
] = '';
924 hookEvent( 'load', os_MWSuggestInit
);