Index: trunk/extensions/UploadWizard/UploadWizardHooks.php |
— | — | @@ -28,6 +28,8 @@ |
29 | 29 | 'resources/jquery/jquery.arrowSteps.js', |
30 | 30 | 'resources/jquery/jquery.autocomplete.js', |
31 | 31 | 'resources/jquery/jquery.spinner.js', |
| 32 | + 'resources/jquery/jquery.autoEllipsis.js', |
| 33 | + 'resources/jquery/jquery.suggestions.js', |
32 | 34 | |
33 | 35 | // mediawiki-specific interface helper (relies on mediawiki globals) |
34 | 36 | 'resources/jquery/jquery.mwCoolCats.js', |
— | — | @@ -115,6 +117,7 @@ |
116 | 118 | ), |
117 | 119 | 'styles' => array( |
118 | 120 | 'resources/jquery/jquery.tipsy.css', |
| 121 | + 'resources/jquery/jquery.suggestions.css', |
119 | 122 | 'resources/uploadWizard.css', |
120 | 123 | 'resources/jquery/jquery.arrowSteps.css', |
121 | 124 | 'resources/jquery/jquery.mwCoolCats.css', |
Index: trunk/extensions/UploadWizard/resources/jquery/jquery.suggestions.css |
— | — | @@ -0,0 +1,68 @@ |
| 2 | +/* suggestions plugin */ |
| 3 | + |
| 4 | +.suggestions { |
| 5 | + overflow: hidden; |
| 6 | + position: absolute; |
| 7 | + top: 0px; |
| 8 | + left: 0px; |
| 9 | + width: 0px; |
| 10 | + border: none; |
| 11 | + z-index: 99; |
| 12 | + padding: 0; |
| 13 | + margin: -1px 0 0 0; |
| 14 | +} |
| 15 | +.suggestions-special { |
| 16 | + position: relative; |
| 17 | + background-color: Window; |
| 18 | + font-size: 0.8em; |
| 19 | + cursor: pointer; |
| 20 | + border: solid 1px #aaaaaa; |
| 21 | + padding: 0; |
| 22 | + margin: 0; |
| 23 | + margin-top: -2px; |
| 24 | + display: none; |
| 25 | + padding: 0.25em 0.25em; |
| 26 | + line-height: 1.25em; |
| 27 | +} |
| 28 | +.suggestions-results { |
| 29 | + background-color: white; |
| 30 | + background-color: Window; |
| 31 | + font-size: 0.8em; |
| 32 | + cursor: pointer; |
| 33 | + border: solid 1px #aaaaaa; |
| 34 | + padding: 0; |
| 35 | + margin: 0; |
| 36 | +} |
| 37 | +.suggestions-result { |
| 38 | + color: black; |
| 39 | + color: WindowText; |
| 40 | + margin: 0; |
| 41 | + line-height: 1.5em; |
| 42 | + padding: 0.01em 0.25em; |
| 43 | +} |
| 44 | +.suggestions-result-current { |
| 45 | + background-color: #4C59A6; |
| 46 | + background-color: Highlight; |
| 47 | + color: white; |
| 48 | + color: HighlightText; |
| 49 | +} |
| 50 | +.suggestions-special .special-label { |
| 51 | + font-size: 0.8em; |
| 52 | + color: gray; |
| 53 | +} |
| 54 | +.suggestions-special .special-query { |
| 55 | + color: black; |
| 56 | + font-style: italic; |
| 57 | +} |
| 58 | +.suggestions-special .special-hover { |
| 59 | + background-color: silver; |
| 60 | +} |
| 61 | +.suggestions-result-current .special-label, |
| 62 | +.suggestions-result-current .special-query { |
| 63 | + color: white; |
| 64 | + color: HighlightText; |
| 65 | +} |
| 66 | +.autoellipsis-matched, |
| 67 | +.highlight { |
| 68 | + font-weight: bold; |
| 69 | +} |
\ No newline at end of file |
Property changes on: trunk/extensions/UploadWizard/resources/jquery/jquery.suggestions.css |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 70 | + native |
Index: trunk/extensions/UploadWizard/resources/jquery/dir.combined.css |
— | — | @@ -28,7 +28,74 @@ |
29 | 29 | /* slightly different syntax for IE8 */ |
30 | 30 | -ms-filter:"progid:DXImageTransform.Microsoft.dropshadow(OffX=5, OffY=5, Color='gray')"; |
31 | 31 | } |
32 | | -.arrowSteps { |
| 32 | +/* suggestions plugin */ |
| 33 | + |
| 34 | +.suggestions { |
| 35 | + overflow: hidden; |
| 36 | + position: absolute; |
| 37 | + top: 0px; |
| 38 | + left: 0px; |
| 39 | + width: 0px; |
| 40 | + border: none; |
| 41 | + z-index: 99; |
| 42 | + padding: 0; |
| 43 | + margin: -1px 0 0 0; |
| 44 | +} |
| 45 | +.suggestions-special { |
| 46 | + position: relative; |
| 47 | + background-color: Window; |
| 48 | + font-size: 0.8em; |
| 49 | + cursor: pointer; |
| 50 | + border: solid 1px #aaaaaa; |
| 51 | + padding: 0; |
| 52 | + margin: 0; |
| 53 | + margin-top: -2px; |
| 54 | + display: none; |
| 55 | + padding: 0.25em 0.25em; |
| 56 | + line-height: 1.25em; |
| 57 | +} |
| 58 | +.suggestions-results { |
| 59 | + background-color: white; |
| 60 | + background-color: Window; |
| 61 | + font-size: 0.8em; |
| 62 | + cursor: pointer; |
| 63 | + border: solid 1px #aaaaaa; |
| 64 | + padding: 0; |
| 65 | + margin: 0; |
| 66 | +} |
| 67 | +.suggestions-result { |
| 68 | + color: black; |
| 69 | + color: WindowText; |
| 70 | + margin: 0; |
| 71 | + line-height: 1.5em; |
| 72 | + padding: 0.01em 0.25em; |
| 73 | +} |
| 74 | +.suggestions-result-current { |
| 75 | + background-color: #4C59A6; |
| 76 | + background-color: Highlight; |
| 77 | + color: white; |
| 78 | + color: HighlightText; |
| 79 | +} |
| 80 | +.suggestions-special .special-label { |
| 81 | + font-size: 0.8em; |
| 82 | + color: gray; |
| 83 | +} |
| 84 | +.suggestions-special .special-query { |
| 85 | + color: black; |
| 86 | + font-style: italic; |
| 87 | +} |
| 88 | +.suggestions-special .special-hover { |
| 89 | + background-color: silver; |
| 90 | +} |
| 91 | +.suggestions-result-current .special-label, |
| 92 | +.suggestions-result-current .special-query { |
| 93 | + color: white; |
| 94 | + color: HighlightText; |
| 95 | +} |
| 96 | +.autoellipsis-matched, |
| 97 | +.highlight { |
| 98 | + font-weight: bold; |
| 99 | +}.arrowSteps { |
33 | 100 | list-style-type: none; |
34 | 101 | list-style-image: none; |
35 | 102 | border: 1px solid #666666; |
Index: trunk/extensions/UploadWizard/resources/jquery/jquery.suggestions.js |
— | — | @@ -0,0 +1,521 @@ |
| 2 | +/** |
| 3 | + * This plugin provides a generic way to add suggestions to a text box. |
| 4 | + * |
| 5 | + * Usage: |
| 6 | + * |
| 7 | + * Set options: |
| 8 | + * $('#textbox').suggestions( { option1: value1, option2: value2 } ); |
| 9 | + * $('#textbox').suggestions( option, value ); |
| 10 | + * Get option: |
| 11 | + * value = $('#textbox').suggestions( option ); |
| 12 | + * Initialize: |
| 13 | + * $('#textbox').suggestions(); |
| 14 | + * |
| 15 | + * Options: |
| 16 | + * |
| 17 | + * fetch(query): Callback that should fetch suggestions and set the suggestions property. Executed in the context of the |
| 18 | + * textbox |
| 19 | + * Type: Function |
| 20 | + * cancel: Callback function to call when any pending asynchronous suggestions fetches should be canceled. |
| 21 | + * Executed in the context of the textbox |
| 22 | + * Type: Function |
| 23 | + * special: Set of callbacks for rendering and selecting |
| 24 | + * Type: Object of Functions 'render' and 'select' |
| 25 | + * result: Set of callbacks for rendering and selecting |
| 26 | + * Type: Object of Functions 'render' and 'select' |
| 27 | + * $region: jQuery selection of element to place the suggestions below and match width of |
| 28 | + * Type: jQuery Object, Default: $(this) |
| 29 | + * suggestions: Suggestions to display |
| 30 | + * Type: Array of strings |
| 31 | + * maxRows: Maximum number of suggestions to display at one time |
| 32 | + * Type: Number, Range: 1 - 100, Default: 7 |
| 33 | + * delay: Number of ms to wait for the user to stop typing |
| 34 | + * Type: Number, Range: 0 - 1200, Default: 120 |
| 35 | + * submitOnClick: Whether to submit the form containing the textbox when a suggestion is clicked |
| 36 | + * Type: Boolean, Default: false |
| 37 | + * maxExpandFactor: Maximum suggestions box width relative to the textbox width. If set to e.g. 2, the suggestions box |
| 38 | + * will never be grown beyond 2 times the width of the textbox. |
| 39 | + * Type: Number, Range: 1 - infinity, Default: 3 |
| 40 | + * positionFromLeft: Whether to position the suggestion box with the left attribute or the right |
| 41 | + * Type: Boolean, Default: true |
| 42 | + * highlightInput: Whether to hightlight matched portions of the input or not |
| 43 | + * Type: Boolean, Default: false |
| 44 | + */ |
| 45 | +( function( $ ) { |
| 46 | + |
| 47 | +$.suggestions = { |
| 48 | + /** |
| 49 | + * Cancel any delayed updateSuggestions() call and inform the user so |
| 50 | + * they can cancel their result fetching if they use AJAX or something |
| 51 | + */ |
| 52 | + cancel: function( context ) { |
| 53 | + if ( context.data.timerID != null ) { |
| 54 | + clearTimeout( context.data.timerID ); |
| 55 | + } |
| 56 | + if ( typeof context.config.cancel == 'function' ) { |
| 57 | + context.config.cancel.call( context.data.$textbox ); |
| 58 | + } |
| 59 | + }, |
| 60 | + /** |
| 61 | + * Restore the text the user originally typed in the textbox, before it was overwritten by highlight(). This |
| 62 | + * restores the value the currently displayed suggestions are based on, rather than the value just before |
| 63 | + * highlight() overwrote it; the former is arguably slightly more sensible. |
| 64 | + */ |
| 65 | + restore: function( context ) { |
| 66 | + context.data.$textbox.val( context.data.prevText ); |
| 67 | + }, |
| 68 | + /** |
| 69 | + * Ask the user-specified callback for new suggestions. Any previous delayed call to this function still pending |
| 70 | + * will be canceled. If the value in the textbox hasn't changed since the last time suggestions were fetched, this |
| 71 | + * function does nothing. |
| 72 | + * @param {Boolean} delayed Whether or not to delay this by the currently configured amount of time |
| 73 | + */ |
| 74 | + update: function( context, delayed ) { |
| 75 | + // Only fetch if the value in the textbox changed |
| 76 | + function maybeFetch() { |
| 77 | + if ( context.data.$textbox.val() !== context.data.prevText ) { |
| 78 | + context.data.prevText = context.data.$textbox.val(); |
| 79 | + if ( typeof context.config.fetch == 'function' ) { |
| 80 | + context.config.fetch.call( context.data.$textbox, context.data.$textbox.val() ); |
| 81 | + } |
| 82 | + } |
| 83 | + } |
| 84 | + // Cancel previous call |
| 85 | + if ( context.data.timerID != null ) { |
| 86 | + clearTimeout( context.data.timerID ); |
| 87 | + } |
| 88 | + if ( delayed ) { |
| 89 | + // Start a new asynchronous call |
| 90 | + context.data.timerID = setTimeout( maybeFetch, context.config.delay ); |
| 91 | + } else { |
| 92 | + maybeFetch(); |
| 93 | + } |
| 94 | + $.suggestions.special( context ); |
| 95 | + }, |
| 96 | + special: function( context ) { |
| 97 | + // Allow custom rendering - but otherwise don't do any rendering |
| 98 | + if ( typeof context.config.special.render == 'function' ) { |
| 99 | + // Wait for the browser to update the value |
| 100 | + setTimeout( function() { |
| 101 | + // Render special |
| 102 | + $special = context.data.$container.find( '.suggestions-special' ); |
| 103 | + context.config.special.render.call( $special, context.data.$textbox.val() ); |
| 104 | + }, 1 ); |
| 105 | + } |
| 106 | + }, |
| 107 | + /** |
| 108 | + * Sets the value of a property, and updates the widget accordingly |
| 109 | + * @param {String} property Name of property |
| 110 | + * @param {Mixed} value Value to set property with |
| 111 | + */ |
| 112 | + configure: function( context, property, value ) { |
| 113 | + // Validate creation using fallback values |
| 114 | + switch( property ) { |
| 115 | + case 'fetch': |
| 116 | + case 'cancel': |
| 117 | + case 'special': |
| 118 | + case 'result': |
| 119 | + case '$region': |
| 120 | + context.config[property] = value; |
| 121 | + break; |
| 122 | + case 'suggestions': |
| 123 | + context.config[property] = value; |
| 124 | + // Update suggestions |
| 125 | + if ( typeof context.data !== 'undefined' ) { |
| 126 | + if ( context.data.$textbox.val().length == 0 ) { |
| 127 | + // Hide the div when no suggestion exist |
| 128 | + context.data.$container.hide(); |
| 129 | + } else { |
| 130 | + // Rebuild the suggestions list |
| 131 | + context.data.$container.show(); |
| 132 | + // Update the size and position of the list |
| 133 | + var newCSS = { |
| 134 | + 'top': context.config.$region.offset().top + context.config.$region.outerHeight(), |
| 135 | + 'bottom': 'auto', |
| 136 | + 'width': context.config.$region.outerWidth(), |
| 137 | + 'height': 'auto' |
| 138 | + } |
| 139 | + if ( context.config.positionFromLeft ) { |
| 140 | + newCSS['left'] = context.config.$region.offset().left; |
| 141 | + newCSS['right'] = 'auto'; |
| 142 | + } else { |
| 143 | + newCSS['left'] = 'auto'; |
| 144 | + newCSS['right'] = $( 'body' ).width() - ( context.config.$region.offset().left + context.config.$region.outerWidth() ); |
| 145 | + } |
| 146 | + context.data.$container.css( newCSS ); |
| 147 | + var $results = context.data.$container.children( '.suggestions-results' ); |
| 148 | + $results.empty(); |
| 149 | + var expWidth = -1; |
| 150 | + var $autoEllipseMe = $( [] ); |
| 151 | + var matchedText = null; |
| 152 | + for ( var i = 0; i < context.config.suggestions.length; i++ ) { |
| 153 | + var text = context.config.suggestions[i]; |
| 154 | + var $result = $( '<div />' ) |
| 155 | + .addClass( 'suggestions-result' ) |
| 156 | + .attr( 'rel', i ) |
| 157 | + .data( 'text', context.config.suggestions[i] ) |
| 158 | + .mousemove( function( e ) { |
| 159 | + context.data.selectedWithMouse = true; |
| 160 | + $.suggestions.highlight( |
| 161 | + context, $(this).closest( '.suggestions-results div' ), false |
| 162 | + ); |
| 163 | + } ) |
| 164 | + .appendTo( $results ); |
| 165 | + // Allow custom rendering |
| 166 | + if ( typeof context.config.result.render == 'function' ) { |
| 167 | + context.config.result.render.call( $result, context.config.suggestions[i] ); |
| 168 | + } else { |
| 169 | + // Add <span> with text |
| 170 | + if( context.config.highlightInput ) { |
| 171 | + matchedText = context.data.prevText; |
| 172 | + } |
| 173 | + $result.append( $( '<span />' ) |
| 174 | + .css( 'whiteSpace', 'nowrap' ) |
| 175 | + .text( text ) |
| 176 | + ); |
| 177 | + |
| 178 | + // Widen results box if needed |
| 179 | + // New width is only calculated here, applied later |
| 180 | + var $span = $result.children( 'span' ); |
| 181 | + if ( $span.outerWidth() > $result.width() && $span.outerWidth() > expWidth ) { |
| 182 | + // factor in any padding, margin, or border space on the parent |
| 183 | + expWidth = $span.outerWidth() + ( context.data.$container.width() - $span.parent().width()); |
| 184 | + } |
| 185 | + $autoEllipseMe = $autoEllipseMe.add( $result ); |
| 186 | + } |
| 187 | + } |
| 188 | + // Apply new width for results box, if any |
| 189 | + if ( expWidth > context.data.$container.width() ) { |
| 190 | + var maxWidth = context.config.maxExpandFactor*context.data.$textbox.width(); |
| 191 | + context.data.$container.width( Math.min( expWidth, maxWidth ) ); |
| 192 | + } |
| 193 | + // autoEllipse the results. Has to be done after changing the width |
| 194 | + $autoEllipseMe.autoEllipsis( { hasSpan: true, tooltip: true, matchText: matchedText } ); |
| 195 | + } |
| 196 | + } |
| 197 | + break; |
| 198 | + case 'maxRows': |
| 199 | + context.config[property] = Math.max( 1, Math.min( 100, value ) ); |
| 200 | + break; |
| 201 | + case 'delay': |
| 202 | + context.config[property] = Math.max( 0, Math.min( 1200, value ) ); |
| 203 | + break; |
| 204 | + case 'maxExpandFactor': |
| 205 | + context.config[property] = Math.max( 1, value ); |
| 206 | + break; |
| 207 | + case 'submitOnClick': |
| 208 | + case 'positionFromLeft': |
| 209 | + case 'highlightInput': |
| 210 | + context.config[property] = value ? true : false; |
| 211 | + break; |
| 212 | + } |
| 213 | + }, |
| 214 | + /** |
| 215 | + * Highlight a result in the results table |
| 216 | + * @param result <tr> to highlight: jQuery object, or 'prev' or 'next' |
| 217 | + * @param updateTextbox If true, put the suggestion in the textbox |
| 218 | + */ |
| 219 | + highlight: function( context, result, updateTextbox ) { |
| 220 | + var selected = context.data.$container.find( '.suggestions-result-current' ); |
| 221 | + if ( !result.get || selected.get( 0 ) != result.get( 0 ) ) { |
| 222 | + if ( result == 'prev' ) { |
| 223 | + if( selected.is( '.suggestions-special' ) ) { |
| 224 | + result = context.data.$container.find( '.suggestions-result:last' ) |
| 225 | + } else { |
| 226 | + result = selected.prev(); |
| 227 | + if ( selected.length == 0 ) { |
| 228 | + // we are at the begginning, so lets jump to the last item |
| 229 | + if ( context.data.$container.find( '.suggestions-special' ).html() != "" ) { |
| 230 | + result = context.data.$container.find( '.suggestions-special' ); |
| 231 | + } else { |
| 232 | + result = context.data.$container.find( '.suggestions-results div:last' ); |
| 233 | + } |
| 234 | + } |
| 235 | + } |
| 236 | + } else if ( result == 'next' ) { |
| 237 | + if ( selected.length == 0 ) { |
| 238 | + // No item selected, go to the first one |
| 239 | + result = context.data.$container.find( '.suggestions-results div:first' ); |
| 240 | + if ( result.length == 0 && context.data.$container.find( '.suggestions-special' ).html() != "" ) { |
| 241 | + // No suggestion exists, go to the special one directly |
| 242 | + result = context.data.$container.find( '.suggestions-special' ); |
| 243 | + } |
| 244 | + } else { |
| 245 | + result = selected.next(); |
| 246 | + if ( selected.is( '.suggestions-special' ) ) { |
| 247 | + result = $( [] ); |
| 248 | + } else if ( |
| 249 | + result.length == 0 && |
| 250 | + context.data.$container.find( '.suggestions-special' ).html() != "" |
| 251 | + ) { |
| 252 | + // We were at the last item, jump to the specials! |
| 253 | + result = context.data.$container.find( '.suggestions-special' ); |
| 254 | + } |
| 255 | + } |
| 256 | + } |
| 257 | + selected.removeClass( 'suggestions-result-current' ); |
| 258 | + result.addClass( 'suggestions-result-current' ); |
| 259 | + } |
| 260 | + if ( updateTextbox ) { |
| 261 | + if ( result.length == 0 || result.is( '.suggestions-special' ) ) { |
| 262 | + $.suggestions.restore( context ); |
| 263 | + } else { |
| 264 | + context.data.$textbox.val( result.data( 'text' ) ); |
| 265 | + // .val() doesn't call any event handlers, so |
| 266 | + // let the world know what happened |
| 267 | + context.data.$textbox.change(); |
| 268 | + } |
| 269 | + context.data.$textbox.trigger( 'change' ); |
| 270 | + } |
| 271 | + }, |
| 272 | + /** |
| 273 | + * Respond to keypress event |
| 274 | + * @param {Integer} key Code of key pressed |
| 275 | + */ |
| 276 | + keypress: function( e, context, key ) { |
| 277 | + var wasVisible = context.data.$container.is( ':visible' ); |
| 278 | + var preventDefault = false; |
| 279 | + switch ( key ) { |
| 280 | + // Arrow down |
| 281 | + case 40: |
| 282 | + if ( wasVisible ) { |
| 283 | + $.suggestions.highlight( context, 'next', true ); |
| 284 | + context.data.selectedWithMouse = false; |
| 285 | + } else { |
| 286 | + $.suggestions.update( context, false ); |
| 287 | + } |
| 288 | + preventDefault = true; |
| 289 | + break; |
| 290 | + // Arrow up |
| 291 | + case 38: |
| 292 | + if ( wasVisible ) { |
| 293 | + $.suggestions.highlight( context, 'prev', true ); |
| 294 | + context.data.selectedWithMouse = false; |
| 295 | + } |
| 296 | + preventDefault = wasVisible; |
| 297 | + break; |
| 298 | + // Escape |
| 299 | + case 27: |
| 300 | + context.data.$container.hide(); |
| 301 | + $.suggestions.restore( context ); |
| 302 | + $.suggestions.cancel( context ); |
| 303 | + context.data.$textbox.trigger( 'change' ); |
| 304 | + preventDefault = wasVisible; |
| 305 | + break; |
| 306 | + // Enter |
| 307 | + case 13: |
| 308 | + context.data.$container.hide(); |
| 309 | + preventDefault = wasVisible; |
| 310 | + selected = context.data.$container.find( '.suggestions-result-current' ); |
| 311 | + if ( selected.size() == 0 || context.data.selectedWithMouse ) { |
| 312 | + // if nothing is selected OR if something was selected with the mouse, |
| 313 | + // cancel any current requests and submit the form |
| 314 | + $.suggestions.cancel( context ); |
| 315 | + context.config.$region.closest( 'form' ).submit(); |
| 316 | + } else if ( selected.is( '.suggestions-special' ) ) { |
| 317 | + if ( typeof context.config.special.select == 'function' ) { |
| 318 | + context.config.special.select.call( selected, context.data.$textbox ); |
| 319 | + } |
| 320 | + } else { |
| 321 | + if ( typeof context.config.result.select == 'function' ) { |
| 322 | + $.suggestions.highlight( context, selected, true ); |
| 323 | + context.config.result.select.call( selected, context.data.$textbox ); |
| 324 | + } else { |
| 325 | + $.suggestions.highlight( context, selected, true ); |
| 326 | + } |
| 327 | + } |
| 328 | + break; |
| 329 | + default: |
| 330 | + $.suggestions.update( context, true ); |
| 331 | + break; |
| 332 | + } |
| 333 | + if ( preventDefault ) { |
| 334 | + e.preventDefault(); |
| 335 | + e.stopImmediatePropagation(); |
| 336 | + } |
| 337 | + } |
| 338 | +}; |
| 339 | +$.fn.suggestions = function() { |
| 340 | + |
| 341 | + // Multi-context fields |
| 342 | + var returnValue = null; |
| 343 | + var args = arguments; |
| 344 | + |
| 345 | + $(this).each( function() { |
| 346 | + |
| 347 | + /* Construction / Loading */ |
| 348 | + |
| 349 | + var context = $(this).data( 'suggestions-context' ); |
| 350 | + if ( typeof context == 'undefined' || context == null ) { |
| 351 | + context = { |
| 352 | + config: { |
| 353 | + 'fetch' : function() {}, |
| 354 | + 'cancel': function() {}, |
| 355 | + 'special': {}, |
| 356 | + 'result': {}, |
| 357 | + '$region': $(this), |
| 358 | + 'suggestions': [], |
| 359 | + 'maxRows': 7, |
| 360 | + 'delay': 120, |
| 361 | + 'submitOnClick': false, |
| 362 | + 'maxExpandFactor': 3, |
| 363 | + 'positionFromLeft': true, |
| 364 | + 'highlightInput': false |
| 365 | + } |
| 366 | + }; |
| 367 | + } |
| 368 | + |
| 369 | + /* API */ |
| 370 | + |
| 371 | + // Handle various calling styles |
| 372 | + if ( args.length > 0 ) { |
| 373 | + if ( typeof args[0] == 'object' ) { |
| 374 | + // Apply set of properties |
| 375 | + for ( var key in args[0] ) { |
| 376 | + $.suggestions.configure( context, key, args[0][key] ); |
| 377 | + } |
| 378 | + } else if ( typeof args[0] == 'string' ) { |
| 379 | + if ( args.length > 1 ) { |
| 380 | + // Set property values |
| 381 | + $.suggestions.configure( context, args[0], args[1] ); |
| 382 | + } else if ( returnValue == null ) { |
| 383 | + // Get property values, but don't give access to internal data - returns only the first |
| 384 | + returnValue = ( args[0] in context.config ? undefined : context.config[args[0]] ); |
| 385 | + } |
| 386 | + } |
| 387 | + } |
| 388 | + |
| 389 | + /* Initialization */ |
| 390 | + |
| 391 | + if ( typeof context.data == 'undefined' ) { |
| 392 | + context.data = { |
| 393 | + // ID of running timer |
| 394 | + 'timerID': null, |
| 395 | + // Text in textbox when suggestions were last fetched |
| 396 | + 'prevText': null, |
| 397 | + // Number of results visible without scrolling |
| 398 | + 'visibleResults': 0, |
| 399 | + // Suggestion the last mousedown event occured on |
| 400 | + 'mouseDownOn': $( [] ), |
| 401 | + '$textbox': $(this), |
| 402 | + 'selectedWithMouse': false |
| 403 | + }; |
| 404 | + // Setup the css for positioning the results box |
| 405 | + var newCSS = { |
| 406 | + 'top': Math.round( context.data.$textbox.offset().top + context.data.$textbox.outerHeight() ), |
| 407 | + 'width': context.data.$textbox.outerWidth(), |
| 408 | + 'display': 'none' |
| 409 | + } |
| 410 | + if ( context.config.positionFromLeft ) { |
| 411 | + newCSS['left'] = context.config.$region.offset().left; |
| 412 | + newCSS['right'] = 'auto'; |
| 413 | + } else { |
| 414 | + newCSS['left'] = 'auto'; |
| 415 | + newCSS['right'] = $( 'body' ).width() - ( context.config.$region.offset().left + context.config.$region.outerWidth() ); |
| 416 | + } |
| 417 | + |
| 418 | + context.data.$container = $( '<div />' ) |
| 419 | + .css( newCSS ) |
| 420 | + .addClass( 'suggestions' ) |
| 421 | + .append( |
| 422 | + $( '<div />' ).addClass( 'suggestions-results' ) |
| 423 | + // Can't use click() because the container div is hidden when the textbox loses focus. Instead, |
| 424 | + // listen for a mousedown followed by a mouseup on the same div |
| 425 | + .mousedown( function( e ) { |
| 426 | + context.data.mouseDownOn = $( e.target ).closest( '.suggestions-results div' ); |
| 427 | + } ) |
| 428 | + .mouseup( function( e ) { |
| 429 | + var $result = $( e.target ).closest( '.suggestions-results div' ); |
| 430 | + var $other = context.data.mouseDownOn; |
| 431 | + context.data.mouseDownOn = $( [] ); |
| 432 | + if ( $result.get( 0 ) != $other.get( 0 ) ) { |
| 433 | + return; |
| 434 | + } |
| 435 | + $.suggestions.highlight( context, $result, true ); |
| 436 | + context.data.$container.hide(); |
| 437 | + if ( typeof context.config.result.select == 'function' ) { |
| 438 | + context.config.result.select.call( $result, context.data.$textbox ); |
| 439 | + } |
| 440 | + context.data.$textbox.focus(); |
| 441 | + } ) |
| 442 | + ) |
| 443 | + .append( |
| 444 | + $( '<div />' ).addClass( 'suggestions-special' ) |
| 445 | + // Can't use click() because the container div is hidden when the textbox loses focus. Instead, |
| 446 | + // listen for a mousedown followed by a mouseup on the same div |
| 447 | + .mousedown( function( e ) { |
| 448 | + context.data.mouseDownOn = $( e.target ).closest( '.suggestions-special' ); |
| 449 | + } ) |
| 450 | + .mouseup( function( e ) { |
| 451 | + var $special = $( e.target ).closest( '.suggestions-special' ); |
| 452 | + var $other = context.data.mouseDownOn; |
| 453 | + context.data.mouseDownOn = $( [] ); |
| 454 | + if ( $special.get( 0 ) != $other.get( 0 ) ) { |
| 455 | + return; |
| 456 | + } |
| 457 | + context.data.$container.hide(); |
| 458 | + if ( typeof context.config.special.select == 'function' ) { |
| 459 | + context.config.special.select.call( $special, context.data.$textbox ); |
| 460 | + } |
| 461 | + context.data.$textbox.focus(); |
| 462 | + } ) |
| 463 | + .mousemove( function( e ) { |
| 464 | + context.data.selectedWithMouse = true; |
| 465 | + $.suggestions.highlight( |
| 466 | + context, $( e.target ).closest( '.suggestions-special' ), false |
| 467 | + ); |
| 468 | + } ) |
| 469 | + ) |
| 470 | + .appendTo( $( 'body' ) ); |
| 471 | + $(this) |
| 472 | + // Stop browser autocomplete from interfering |
| 473 | + .attr( 'autocomplete', 'off') |
| 474 | + .keydown( function( e ) { |
| 475 | + // Store key pressed to handle later |
| 476 | + context.data.keypressed = ( e.keyCode == undefined ) ? e.which : e.keyCode; |
| 477 | + context.data.keypressedCount = 0; |
| 478 | + |
| 479 | + switch ( context.data.keypressed ) { |
| 480 | + // This preventDefault logic is duplicated from |
| 481 | + // $.suggestions.keypress(), which sucks |
| 482 | + case 40: |
| 483 | + e.preventDefault(); |
| 484 | + e.stopImmediatePropagation(); |
| 485 | + break; |
| 486 | + case 38: |
| 487 | + case 27: |
| 488 | + case 13: |
| 489 | + if ( context.data.$container.is( ':visible' ) ) { |
| 490 | + e.preventDefault(); |
| 491 | + e.stopImmediatePropagation(); |
| 492 | + } |
| 493 | + } |
| 494 | + } ) |
| 495 | + .keypress( function( e ) { |
| 496 | + context.data.keypressedCount++; |
| 497 | + $.suggestions.keypress( e, context, context.data.keypressed ); |
| 498 | + } ) |
| 499 | + .keyup( function( e ) { |
| 500 | + // Some browsers won't throw keypress() for arrow keys. If we got a keydown and a keyup without a |
| 501 | + // keypress in between, solve it |
| 502 | + if ( context.data.keypressedCount == 0 ) { |
| 503 | + $.suggestions.keypress( e, context, context.data.keypressed ); |
| 504 | + } |
| 505 | + } ) |
| 506 | + .blur( function() { |
| 507 | + // When losing focus because of a mousedown |
| 508 | + // on a suggestion, don't hide the suggestions |
| 509 | + if ( context.data.mouseDownOn.length > 0 ) { |
| 510 | + return; |
| 511 | + } |
| 512 | + context.data.$container.hide(); |
| 513 | + $.suggestions.cancel( context ); |
| 514 | + } ); |
| 515 | + } |
| 516 | + // Store the context for next time |
| 517 | + $(this).data( 'suggestions-context', context ); |
| 518 | + } ); |
| 519 | + return returnValue !== null ? returnValue : $(this); |
| 520 | +}; |
| 521 | + |
| 522 | +} )( jQuery ); |
Property changes on: trunk/extensions/UploadWizard/resources/jquery/jquery.suggestions.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 523 | + native |
Index: trunk/extensions/UploadWizard/resources/jquery/jquery.autoEllipsis.js |
— | — | @@ -0,0 +1,131 @@ |
| 2 | +/** |
| 3 | + * Plugin that automatically truncates the plain text contents of an element and adds an ellipsis |
| 4 | + */ |
| 5 | +( function( $ ) { |
| 6 | + |
| 7 | +// Cache ellipsed substrings for every string-width combination |
| 8 | +var cache = { }; |
| 9 | +// Use a seperate cache when match highlighting is enabled |
| 10 | +var matchTextCache = { }; |
| 11 | + |
| 12 | +$.fn.autoEllipsis = function( options ) { |
| 13 | + options = $.extend( { |
| 14 | + 'position': 'center', |
| 15 | + 'tooltip': false, |
| 16 | + 'restoreText': false, |
| 17 | + 'hasSpan': false, |
| 18 | + 'matchText': null |
| 19 | + }, options ); |
| 20 | + $(this).each( function() { |
| 21 | + var $this = $(this); |
| 22 | + if ( options.restoreText ) { |
| 23 | + if ( ! $this.data( 'autoEllipsis.originalText' ) ) { |
| 24 | + $this.data( 'autoEllipsis.originalText', $this.text() ); |
| 25 | + } else { |
| 26 | + $this.text( $this.data( 'autoEllipsis.originalText' ) ); |
| 27 | + } |
| 28 | + } |
| 29 | + |
| 30 | + // container element - used for measuring against |
| 31 | + var $container = $this; |
| 32 | + // trimmable text element - only the text within this element will be trimmed |
| 33 | + var $trimmableText = null; |
| 34 | + // protected text element - the width of this element is counted, but next is never trimmed from it |
| 35 | + var $protectedText = null; |
| 36 | + |
| 37 | + if ( options.hasSpan ) { |
| 38 | + $trimmableText = $this.children( options.selector ); |
| 39 | + } else { |
| 40 | + $trimmableText = $( '<span />' ) |
| 41 | + .css( 'whiteSpace', 'nowrap' ) |
| 42 | + .text( $this.text() ); |
| 43 | + $this |
| 44 | + .empty() |
| 45 | + .append( $trimmableText ); |
| 46 | + } |
| 47 | + |
| 48 | + var text = $container.text(); |
| 49 | + var trimmableText = $trimmableText.text(); |
| 50 | + var w = $container.width(); |
| 51 | + var pw = $protectedText ? $protectedText.width() : 0; |
| 52 | + // Try cache |
| 53 | + if ( !( text in cache ) ) { |
| 54 | + cache[text] = {}; |
| 55 | + } |
| 56 | + if ( options.matchText && !( text in matchTextCache ) ) { |
| 57 | + matchTextCache[text] = {}; |
| 58 | + } |
| 59 | + if ( options.matchText && !( options.matchText in matchTextCache[text] ) ) { |
| 60 | + matchTextCache[text][options.matchText] = {}; |
| 61 | + } |
| 62 | + if ( !options.matchText && w in cache[text] ) { |
| 63 | + $container.html( cache[text][w] ); |
| 64 | + if ( options.tooltip ) |
| 65 | + $container.attr( 'title', text ); |
| 66 | + return; |
| 67 | + } |
| 68 | + if( options.matchText && options.matchText in matchTextCache[text] && w in matchTextCache[text][options.matchText] ) { |
| 69 | + $container.html( matchTextCache[text][options.matchText][w] ); |
| 70 | + if ( options.tooltip ) |
| 71 | + $container.attr( 'title', text ); |
| 72 | + return; |
| 73 | + } |
| 74 | + if ( $trimmableText.width() + pw > w ) { |
| 75 | + switch ( options.position ) { |
| 76 | + case 'right': |
| 77 | + // Use binary search-like technique for efficiency |
| 78 | + var l = 0, r = trimmableText.length; |
| 79 | + do { |
| 80 | + var m = Math.ceil( ( l + r ) / 2 ); |
| 81 | + $trimmableText.text( trimmableText.substr( 0, m ) + '...' ); |
| 82 | + if ( $trimmableText.width() + pw > w ) { |
| 83 | + // Text is too long |
| 84 | + r = m - 1; |
| 85 | + } else { |
| 86 | + l = m; |
| 87 | + } |
| 88 | + } while ( l < r ); |
| 89 | + $trimmableText.text( trimmableText.substr( 0, l ) + '...' ); |
| 90 | + break; |
| 91 | + case 'center': |
| 92 | + // TODO: Use binary search like for 'right' |
| 93 | + var i = [Math.round( trimmableText.length / 2 ), Math.round( trimmableText.length / 2 )]; |
| 94 | + var side = 1; // Begin with making the end shorter |
| 95 | + while ( $trimmableText.outerWidth() + pw > w && i[0] > 0 ) { |
| 96 | + $trimmableText.text( trimmableText.substr( 0, i[0] ) + '...' + trimmableText.substr( i[1] ) ); |
| 97 | + // Alternate between trimming the end and begining |
| 98 | + if ( side == 0 ) { |
| 99 | + // Make the begining shorter |
| 100 | + i[0]--; |
| 101 | + side = 1; |
| 102 | + } else { |
| 103 | + // Make the end shorter |
| 104 | + i[1]++; |
| 105 | + side = 0; |
| 106 | + } |
| 107 | + } |
| 108 | + break; |
| 109 | + case 'left': |
| 110 | + // TODO: Use binary search like for 'right' |
| 111 | + var r = 0; |
| 112 | + while ( $trimmableText.outerWidth() + pw > w && r < trimmableText.length ) { |
| 113 | + $trimmableText.text( '...' + trimmableText.substr( r ) ); |
| 114 | + r++; |
| 115 | + } |
| 116 | + break; |
| 117 | + } |
| 118 | + } |
| 119 | + if ( options.tooltip ) { |
| 120 | + $container.attr( 'title', text ); |
| 121 | + } |
| 122 | + if ( options.matchText ) { |
| 123 | + $container.highlightText( options.matchText ); |
| 124 | + matchTextCache[text][options.matchText][w] = $container.html(); |
| 125 | + } else { |
| 126 | + cache[text][w] = $container.html(); |
| 127 | + } |
| 128 | + |
| 129 | + } ); |
| 130 | +}; |
| 131 | + |
| 132 | +} )( jQuery ); |
Property changes on: trunk/extensions/UploadWizard/resources/jquery/jquery.autoEllipsis.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 133 | + native |
Index: trunk/extensions/UploadWizard/resources/jquery/dir.combined.min.css |
— | — | @@ -15,7 +15,16 @@ |
16 | 16 | -webkit-box-shadow:5px 5px 5px rgba(0,0,0,0.2);-moz-box-shadow:5px 5px 5px rgba(0,0,0,0.2);box-shadow:5px 5px 5px rgba(0,0,0,0.2); |
17 | 17 | filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=5,OffY=5,Color='gray'); |
18 | 18 | -ms-filter:"progid:DXImageTransform.Microsoft.dropshadow(OffX=5,OffY=5,Color='gray')";} |
19 | | -.arrowSteps{list-style-type:none;list-style-image:none;border:1px solid #666666;position:relative;}.arrowSteps li{float:left;padding:0px;margin:0px;border:0 none;}.arrowSteps li div{padding:0.5em;text-align:center;white-space:nowrap;overflow:hidden;}.arrowSteps li.arrow div{background:url(jquery.arrowSteps.divider.png) no-repeat right center;} |
| 19 | +.suggestions{overflow:hidden;position:absolute;top:0px;left:0px;width:0px;border:none;z-index:99;padding:0;margin:-1px 0 0 0;} |
| 20 | +.suggestions-special{position:relative;background-color:Window;font-size:0.8em;cursor:pointer;border:solid 1px #aaaaaa;padding:0;margin:0;margin-top:-2px;display:none;padding:0.25em 0.25em;line-height:1.25em;} |
| 21 | +.suggestions-results{background-color:white;background-color:Window;font-size:0.8em;cursor:pointer;border:solid 1px #aaaaaa;padding:0;margin:0;} |
| 22 | +.suggestions-result{color:black;color:WindowText;margin:0;line-height:1.5em;padding:0.01em 0.25em;} |
| 23 | +.suggestions-result-current{background-color:#4C59A6;background-color:Highlight;color:white;color:HighlightText;} |
| 24 | +.suggestions-special .special-label{font-size:0.8em;color:gray;} |
| 25 | +.suggestions-special .special-query{color:black;font-style:italic;} |
| 26 | +.suggestions-special .special-hover{background-color:silver;} |
| 27 | +.suggestions-result-current .special-label,.suggestions-result-current .special-query{color:white;color:HighlightText;} |
| 28 | +.autoellipsis-matched,.highlight{font-weight:bold;}.arrowSteps{list-style-type:none;list-style-image:none;border:1px solid #666666;position:relative;}.arrowSteps li{float:left;padding:0px;margin:0px;border:0 none;}.arrowSteps li div{padding:0.5em;text-align:center;white-space:nowrap;overflow:hidden;}.arrowSteps li.arrow div{background:url(jquery.arrowSteps.divider.png) no-repeat right center;} |
20 | 29 | .arrowSteps li.arrow.tail div{background:url(jquery.arrowSteps.tail.png) no-repeat right center;} |
21 | 30 | .arrowSteps li.head div{background:url(jquery.arrowSteps.head.png) no-repeat left center;font-weight:bold;} |
22 | 31 | .arrowSteps li.arrow.head div{background:url(jquery.arrowSteps.head.png) no-repeat right center;} |
Index: trunk/extensions/UploadWizard/resources/combined.js |
— | — | @@ -4412,6 +4412,527 @@ |
4413 | 4413 | return $spinner; |
4414 | 4414 | } |
4415 | 4415 | } )( jQuery ); |
| 4416 | +/** |
| 4417 | + * This plugin provides a generic way to add suggestions to a text box. |
| 4418 | + * |
| 4419 | + * Usage: |
| 4420 | + * |
| 4421 | + * Set options: |
| 4422 | + * $('#textbox').suggestions( { option1: value1, option2: value2 } ); |
| 4423 | + * $('#textbox').suggestions( option, value ); |
| 4424 | + * Get option: |
| 4425 | + * value = $('#textbox').suggestions( option ); |
| 4426 | + * Initialize: |
| 4427 | + * $('#textbox').suggestions(); |
| 4428 | + * |
| 4429 | + * Options: |
| 4430 | + * |
| 4431 | + * fetch(query): Callback that should fetch suggestions and set the suggestions property. Executed in the context of the |
| 4432 | + * textbox |
| 4433 | + * Type: Function |
| 4434 | + * cancel: Callback function to call when any pending asynchronous suggestions fetches should be canceled. |
| 4435 | + * Executed in the context of the textbox |
| 4436 | + * Type: Function |
| 4437 | + * special: Set of callbacks for rendering and selecting |
| 4438 | + * Type: Object of Functions 'render' and 'select' |
| 4439 | + * result: Set of callbacks for rendering and selecting |
| 4440 | + * Type: Object of Functions 'render' and 'select' |
| 4441 | + * $region: jQuery selection of element to place the suggestions below and match width of |
| 4442 | + * Type: jQuery Object, Default: $(this) |
| 4443 | + * suggestions: Suggestions to display |
| 4444 | + * Type: Array of strings |
| 4445 | + * maxRows: Maximum number of suggestions to display at one time |
| 4446 | + * Type: Number, Range: 1 - 100, Default: 7 |
| 4447 | + * delay: Number of ms to wait for the user to stop typing |
| 4448 | + * Type: Number, Range: 0 - 1200, Default: 120 |
| 4449 | + * submitOnClick: Whether to submit the form containing the textbox when a suggestion is clicked |
| 4450 | + * Type: Boolean, Default: false |
| 4451 | + * maxExpandFactor: Maximum suggestions box width relative to the textbox width. If set to e.g. 2, the suggestions box |
| 4452 | + * will never be grown beyond 2 times the width of the textbox. |
| 4453 | + * Type: Number, Range: 1 - infinity, Default: 3 |
| 4454 | + * positionFromLeft: Whether to position the suggestion box with the left attribute or the right |
| 4455 | + * Type: Boolean, Default: true |
| 4456 | + * highlightInput: Whether to hightlight matched portions of the input or not |
| 4457 | + * Type: Boolean, Default: false |
| 4458 | + */ |
| 4459 | +( function( $ ) { |
| 4460 | + |
| 4461 | +$.suggestions = { |
| 4462 | + /** |
| 4463 | + * Cancel any delayed updateSuggestions() call and inform the user so |
| 4464 | + * they can cancel their result fetching if they use AJAX or something |
| 4465 | + */ |
| 4466 | + cancel: function( context ) { |
| 4467 | + if ( context.data.timerID != null ) { |
| 4468 | + clearTimeout( context.data.timerID ); |
| 4469 | + } |
| 4470 | + if ( typeof context.config.cancel == 'function' ) { |
| 4471 | + context.config.cancel.call( context.data.$textbox ); |
| 4472 | + } |
| 4473 | + }, |
| 4474 | + /** |
| 4475 | + * Restore the text the user originally typed in the textbox, before it was overwritten by highlight(). This |
| 4476 | + * restores the value the currently displayed suggestions are based on, rather than the value just before |
| 4477 | + * highlight() overwrote it; the former is arguably slightly more sensible. |
| 4478 | + */ |
| 4479 | + restore: function( context ) { |
| 4480 | + context.data.$textbox.val( context.data.prevText ); |
| 4481 | + }, |
| 4482 | + /** |
| 4483 | + * Ask the user-specified callback for new suggestions. Any previous delayed call to this function still pending |
| 4484 | + * will be canceled. If the value in the textbox hasn't changed since the last time suggestions were fetched, this |
| 4485 | + * function does nothing. |
| 4486 | + * @param {Boolean} delayed Whether or not to delay this by the currently configured amount of time |
| 4487 | + */ |
| 4488 | + update: function( context, delayed ) { |
| 4489 | + // Only fetch if the value in the textbox changed |
| 4490 | + function maybeFetch() { |
| 4491 | + if ( context.data.$textbox.val() !== context.data.prevText ) { |
| 4492 | + context.data.prevText = context.data.$textbox.val(); |
| 4493 | + if ( typeof context.config.fetch == 'function' ) { |
| 4494 | + context.config.fetch.call( context.data.$textbox, context.data.$textbox.val() ); |
| 4495 | + } |
| 4496 | + } |
| 4497 | + } |
| 4498 | + // Cancel previous call |
| 4499 | + if ( context.data.timerID != null ) { |
| 4500 | + clearTimeout( context.data.timerID ); |
| 4501 | + } |
| 4502 | + if ( delayed ) { |
| 4503 | + // Start a new asynchronous call |
| 4504 | + context.data.timerID = setTimeout( maybeFetch, context.config.delay ); |
| 4505 | + } else { |
| 4506 | + maybeFetch(); |
| 4507 | + } |
| 4508 | + $.suggestions.special( context ); |
| 4509 | + }, |
| 4510 | + special: function( context ) { |
| 4511 | + // Allow custom rendering - but otherwise don't do any rendering |
| 4512 | + if ( typeof context.config.special.render == 'function' ) { |
| 4513 | + // Wait for the browser to update the value |
| 4514 | + setTimeout( function() { |
| 4515 | + // Render special |
| 4516 | + $special = context.data.$container.find( '.suggestions-special' ); |
| 4517 | + context.config.special.render.call( $special, context.data.$textbox.val() ); |
| 4518 | + }, 1 ); |
| 4519 | + } |
| 4520 | + }, |
| 4521 | + /** |
| 4522 | + * Sets the value of a property, and updates the widget accordingly |
| 4523 | + * @param {String} property Name of property |
| 4524 | + * @param {Mixed} value Value to set property with |
| 4525 | + */ |
| 4526 | + configure: function( context, property, value ) { |
| 4527 | + // Validate creation using fallback values |
| 4528 | + switch( property ) { |
| 4529 | + case 'fetch': |
| 4530 | + case 'cancel': |
| 4531 | + case 'special': |
| 4532 | + case 'result': |
| 4533 | + case '$region': |
| 4534 | + context.config[property] = value; |
| 4535 | + break; |
| 4536 | + case 'suggestions': |
| 4537 | + context.config[property] = value; |
| 4538 | + // Update suggestions |
| 4539 | + if ( typeof context.data !== 'undefined' ) { |
| 4540 | + if ( context.data.$textbox.val().length == 0 ) { |
| 4541 | + // Hide the div when no suggestion exist |
| 4542 | + context.data.$container.hide(); |
| 4543 | + } else { |
| 4544 | + // Rebuild the suggestions list |
| 4545 | + context.data.$container.show(); |
| 4546 | + // Update the size and position of the list |
| 4547 | + var newCSS = { |
| 4548 | + 'top': context.config.$region.offset().top + context.config.$region.outerHeight(), |
| 4549 | + 'bottom': 'auto', |
| 4550 | + 'width': context.config.$region.outerWidth(), |
| 4551 | + 'height': 'auto' |
| 4552 | + } |
| 4553 | + if ( context.config.positionFromLeft ) { |
| 4554 | + newCSS['left'] = context.config.$region.offset().left; |
| 4555 | + newCSS['right'] = 'auto'; |
| 4556 | + } else { |
| 4557 | + newCSS['left'] = 'auto'; |
| 4558 | + newCSS['right'] = $( 'body' ).width() - ( context.config.$region.offset().left + context.config.$region.outerWidth() ); |
| 4559 | + } |
| 4560 | + context.data.$container.css( newCSS ); |
| 4561 | + var $results = context.data.$container.children( '.suggestions-results' ); |
| 4562 | + $results.empty(); |
| 4563 | + var expWidth = -1; |
| 4564 | + var $autoEllipseMe = $( [] ); |
| 4565 | + var matchedText = null; |
| 4566 | + for ( var i = 0; i < context.config.suggestions.length; i++ ) { |
| 4567 | + var text = context.config.suggestions[i]; |
| 4568 | + var $result = $( '<div />' ) |
| 4569 | + .addClass( 'suggestions-result' ) |
| 4570 | + .attr( 'rel', i ) |
| 4571 | + .data( 'text', context.config.suggestions[i] ) |
| 4572 | + .mousemove( function( e ) { |
| 4573 | + context.data.selectedWithMouse = true; |
| 4574 | + $.suggestions.highlight( |
| 4575 | + context, $(this).closest( '.suggestions-results div' ), false |
| 4576 | + ); |
| 4577 | + } ) |
| 4578 | + .appendTo( $results ); |
| 4579 | + // Allow custom rendering |
| 4580 | + if ( typeof context.config.result.render == 'function' ) { |
| 4581 | + context.config.result.render.call( $result, context.config.suggestions[i] ); |
| 4582 | + } else { |
| 4583 | + // Add <span> with text |
| 4584 | + if( context.config.highlightInput ) { |
| 4585 | + matchedText = context.data.prevText; |
| 4586 | + } |
| 4587 | + $result.append( $( '<span />' ) |
| 4588 | + .css( 'whiteSpace', 'nowrap' ) |
| 4589 | + .text( text ) |
| 4590 | + ); |
| 4591 | + |
| 4592 | + // Widen results box if needed |
| 4593 | + // New width is only calculated here, applied later |
| 4594 | + var $span = $result.children( 'span' ); |
| 4595 | + if ( $span.outerWidth() > $result.width() && $span.outerWidth() > expWidth ) { |
| 4596 | + // factor in any padding, margin, or border space on the parent |
| 4597 | + expWidth = $span.outerWidth() + ( context.data.$container.width() - $span.parent().width()); |
| 4598 | + } |
| 4599 | + $autoEllipseMe = $autoEllipseMe.add( $result ); |
| 4600 | + } |
| 4601 | + } |
| 4602 | + // Apply new width for results box, if any |
| 4603 | + if ( expWidth > context.data.$container.width() ) { |
| 4604 | + var maxWidth = context.config.maxExpandFactor*context.data.$textbox.width(); |
| 4605 | + context.data.$container.width( Math.min( expWidth, maxWidth ) ); |
| 4606 | + } |
| 4607 | + // autoEllipse the results. Has to be done after changing the width |
| 4608 | + $autoEllipseMe.autoEllipsis( { hasSpan: true, tooltip: true, matchText: matchedText } ); |
| 4609 | + } |
| 4610 | + } |
| 4611 | + break; |
| 4612 | + case 'maxRows': |
| 4613 | + context.config[property] = Math.max( 1, Math.min( 100, value ) ); |
| 4614 | + break; |
| 4615 | + case 'delay': |
| 4616 | + context.config[property] = Math.max( 0, Math.min( 1200, value ) ); |
| 4617 | + break; |
| 4618 | + case 'maxExpandFactor': |
| 4619 | + context.config[property] = Math.max( 1, value ); |
| 4620 | + break; |
| 4621 | + case 'submitOnClick': |
| 4622 | + case 'positionFromLeft': |
| 4623 | + case 'highlightInput': |
| 4624 | + context.config[property] = value ? true : false; |
| 4625 | + break; |
| 4626 | + } |
| 4627 | + }, |
| 4628 | + /** |
| 4629 | + * Highlight a result in the results table |
| 4630 | + * @param result <tr> to highlight: jQuery object, or 'prev' or 'next' |
| 4631 | + * @param updateTextbox If true, put the suggestion in the textbox |
| 4632 | + */ |
| 4633 | + highlight: function( context, result, updateTextbox ) { |
| 4634 | + var selected = context.data.$container.find( '.suggestions-result-current' ); |
| 4635 | + if ( !result.get || selected.get( 0 ) != result.get( 0 ) ) { |
| 4636 | + if ( result == 'prev' ) { |
| 4637 | + if( selected.is( '.suggestions-special' ) ) { |
| 4638 | + result = context.data.$container.find( '.suggestions-result:last' ) |
| 4639 | + } else { |
| 4640 | + result = selected.prev(); |
| 4641 | + if ( selected.length == 0 ) { |
| 4642 | + // we are at the begginning, so lets jump to the last item |
| 4643 | + if ( context.data.$container.find( '.suggestions-special' ).html() != "" ) { |
| 4644 | + result = context.data.$container.find( '.suggestions-special' ); |
| 4645 | + } else { |
| 4646 | + result = context.data.$container.find( '.suggestions-results div:last' ); |
| 4647 | + } |
| 4648 | + } |
| 4649 | + } |
| 4650 | + } else if ( result == 'next' ) { |
| 4651 | + if ( selected.length == 0 ) { |
| 4652 | + // No item selected, go to the first one |
| 4653 | + result = context.data.$container.find( '.suggestions-results div:first' ); |
| 4654 | + if ( result.length == 0 && context.data.$container.find( '.suggestions-special' ).html() != "" ) { |
| 4655 | + // No suggestion exists, go to the special one directly |
| 4656 | + result = context.data.$container.find( '.suggestions-special' ); |
| 4657 | + } |
| 4658 | + } else { |
| 4659 | + result = selected.next(); |
| 4660 | + if ( selected.is( '.suggestions-special' ) ) { |
| 4661 | + result = $( [] ); |
| 4662 | + } else if ( |
| 4663 | + result.length == 0 && |
| 4664 | + context.data.$container.find( '.suggestions-special' ).html() != "" |
| 4665 | + ) { |
| 4666 | + // We were at the last item, jump to the specials! |
| 4667 | + result = context.data.$container.find( '.suggestions-special' ); |
| 4668 | + } |
| 4669 | + } |
| 4670 | + } |
| 4671 | + selected.removeClass( 'suggestions-result-current' ); |
| 4672 | + result.addClass( 'suggestions-result-current' ); |
| 4673 | + } |
| 4674 | + if ( updateTextbox ) { |
| 4675 | + if ( result.length == 0 || result.is( '.suggestions-special' ) ) { |
| 4676 | + $.suggestions.restore( context ); |
| 4677 | + } else { |
| 4678 | + context.data.$textbox.val( result.data( 'text' ) ); |
| 4679 | + // .val() doesn't call any event handlers, so |
| 4680 | + // let the world know what happened |
| 4681 | + context.data.$textbox.change(); |
| 4682 | + } |
| 4683 | + context.data.$textbox.trigger( 'change' ); |
| 4684 | + } |
| 4685 | + }, |
| 4686 | + /** |
| 4687 | + * Respond to keypress event |
| 4688 | + * @param {Integer} key Code of key pressed |
| 4689 | + */ |
| 4690 | + keypress: function( e, context, key ) { |
| 4691 | + var wasVisible = context.data.$container.is( ':visible' ); |
| 4692 | + var preventDefault = false; |
| 4693 | + switch ( key ) { |
| 4694 | + // Arrow down |
| 4695 | + case 40: |
| 4696 | + if ( wasVisible ) { |
| 4697 | + $.suggestions.highlight( context, 'next', true ); |
| 4698 | + context.data.selectedWithMouse = false; |
| 4699 | + } else { |
| 4700 | + $.suggestions.update( context, false ); |
| 4701 | + } |
| 4702 | + preventDefault = true; |
| 4703 | + break; |
| 4704 | + // Arrow up |
| 4705 | + case 38: |
| 4706 | + if ( wasVisible ) { |
| 4707 | + $.suggestions.highlight( context, 'prev', true ); |
| 4708 | + context.data.selectedWithMouse = false; |
| 4709 | + } |
| 4710 | + preventDefault = wasVisible; |
| 4711 | + break; |
| 4712 | + // Escape |
| 4713 | + case 27: |
| 4714 | + context.data.$container.hide(); |
| 4715 | + $.suggestions.restore( context ); |
| 4716 | + $.suggestions.cancel( context ); |
| 4717 | + context.data.$textbox.trigger( 'change' ); |
| 4718 | + preventDefault = wasVisible; |
| 4719 | + break; |
| 4720 | + // Enter |
| 4721 | + case 13: |
| 4722 | + context.data.$container.hide(); |
| 4723 | + preventDefault = wasVisible; |
| 4724 | + selected = context.data.$container.find( '.suggestions-result-current' ); |
| 4725 | + if ( selected.size() == 0 || context.data.selectedWithMouse ) { |
| 4726 | + // if nothing is selected OR if something was selected with the mouse, |
| 4727 | + // cancel any current requests and submit the form |
| 4728 | + $.suggestions.cancel( context ); |
| 4729 | + context.config.$region.closest( 'form' ).submit(); |
| 4730 | + } else if ( selected.is( '.suggestions-special' ) ) { |
| 4731 | + if ( typeof context.config.special.select == 'function' ) { |
| 4732 | + context.config.special.select.call( selected, context.data.$textbox ); |
| 4733 | + } |
| 4734 | + } else { |
| 4735 | + if ( typeof context.config.result.select == 'function' ) { |
| 4736 | + $.suggestions.highlight( context, selected, true ); |
| 4737 | + context.config.result.select.call( selected, context.data.$textbox ); |
| 4738 | + } else { |
| 4739 | + $.suggestions.highlight( context, selected, true ); |
| 4740 | + } |
| 4741 | + } |
| 4742 | + break; |
| 4743 | + default: |
| 4744 | + $.suggestions.update( context, true ); |
| 4745 | + break; |
| 4746 | + } |
| 4747 | + if ( preventDefault ) { |
| 4748 | + e.preventDefault(); |
| 4749 | + e.stopImmediatePropagation(); |
| 4750 | + } |
| 4751 | + } |
| 4752 | +}; |
| 4753 | +$.fn.suggestions = function() { |
| 4754 | + |
| 4755 | + // Multi-context fields |
| 4756 | + var returnValue = null; |
| 4757 | + var args = arguments; |
| 4758 | + |
| 4759 | + $(this).each( function() { |
| 4760 | + |
| 4761 | + /* Construction / Loading */ |
| 4762 | + |
| 4763 | + var context = $(this).data( 'suggestions-context' ); |
| 4764 | + if ( typeof context == 'undefined' || context == null ) { |
| 4765 | + context = { |
| 4766 | + config: { |
| 4767 | + 'fetch' : function() {}, |
| 4768 | + 'cancel': function() {}, |
| 4769 | + 'special': {}, |
| 4770 | + 'result': {}, |
| 4771 | + '$region': $(this), |
| 4772 | + 'suggestions': [], |
| 4773 | + 'maxRows': 7, |
| 4774 | + 'delay': 120, |
| 4775 | + 'submitOnClick': false, |
| 4776 | + 'maxExpandFactor': 3, |
| 4777 | + 'positionFromLeft': true, |
| 4778 | + 'highlightInput': false |
| 4779 | + } |
| 4780 | + }; |
| 4781 | + } |
| 4782 | + |
| 4783 | + /* API */ |
| 4784 | + |
| 4785 | + // Handle various calling styles |
| 4786 | + if ( args.length > 0 ) { |
| 4787 | + if ( typeof args[0] == 'object' ) { |
| 4788 | + // Apply set of properties |
| 4789 | + for ( var key in args[0] ) { |
| 4790 | + $.suggestions.configure( context, key, args[0][key] ); |
| 4791 | + } |
| 4792 | + } else if ( typeof args[0] == 'string' ) { |
| 4793 | + if ( args.length > 1 ) { |
| 4794 | + // Set property values |
| 4795 | + $.suggestions.configure( context, args[0], args[1] ); |
| 4796 | + } else if ( returnValue == null ) { |
| 4797 | + // Get property values, but don't give access to internal data - returns only the first |
| 4798 | + returnValue = ( args[0] in context.config ? undefined : context.config[args[0]] ); |
| 4799 | + } |
| 4800 | + } |
| 4801 | + } |
| 4802 | + |
| 4803 | + /* Initialization */ |
| 4804 | + |
| 4805 | + if ( typeof context.data == 'undefined' ) { |
| 4806 | + context.data = { |
| 4807 | + // ID of running timer |
| 4808 | + 'timerID': null, |
| 4809 | + // Text in textbox when suggestions were last fetched |
| 4810 | + 'prevText': null, |
| 4811 | + // Number of results visible without scrolling |
| 4812 | + 'visibleResults': 0, |
| 4813 | + // Suggestion the last mousedown event occured on |
| 4814 | + 'mouseDownOn': $( [] ), |
| 4815 | + '$textbox': $(this), |
| 4816 | + 'selectedWithMouse': false |
| 4817 | + }; |
| 4818 | + // Setup the css for positioning the results box |
| 4819 | + var newCSS = { |
| 4820 | + 'top': Math.round( context.data.$textbox.offset().top + context.data.$textbox.outerHeight() ), |
| 4821 | + 'width': context.data.$textbox.outerWidth(), |
| 4822 | + 'display': 'none' |
| 4823 | + } |
| 4824 | + if ( context.config.positionFromLeft ) { |
| 4825 | + newCSS['left'] = context.config.$region.offset().left; |
| 4826 | + newCSS['right'] = 'auto'; |
| 4827 | + } else { |
| 4828 | + newCSS['left'] = 'auto'; |
| 4829 | + newCSS['right'] = $( 'body' ).width() - ( context.config.$region.offset().left + context.config.$region.outerWidth() ); |
| 4830 | + } |
| 4831 | + |
| 4832 | + context.data.$container = $( '<div />' ) |
| 4833 | + .css( newCSS ) |
| 4834 | + .addClass( 'suggestions' ) |
| 4835 | + .append( |
| 4836 | + $( '<div />' ).addClass( 'suggestions-results' ) |
| 4837 | + // Can't use click() because the container div is hidden when the textbox loses focus. Instead, |
| 4838 | + // listen for a mousedown followed by a mouseup on the same div |
| 4839 | + .mousedown( function( e ) { |
| 4840 | + context.data.mouseDownOn = $( e.target ).closest( '.suggestions-results div' ); |
| 4841 | + } ) |
| 4842 | + .mouseup( function( e ) { |
| 4843 | + var $result = $( e.target ).closest( '.suggestions-results div' ); |
| 4844 | + var $other = context.data.mouseDownOn; |
| 4845 | + context.data.mouseDownOn = $( [] ); |
| 4846 | + if ( $result.get( 0 ) != $other.get( 0 ) ) { |
| 4847 | + return; |
| 4848 | + } |
| 4849 | + $.suggestions.highlight( context, $result, true ); |
| 4850 | + context.data.$container.hide(); |
| 4851 | + if ( typeof context.config.result.select == 'function' ) { |
| 4852 | + context.config.result.select.call( $result, context.data.$textbox ); |
| 4853 | + } |
| 4854 | + context.data.$textbox.focus(); |
| 4855 | + } ) |
| 4856 | + ) |
| 4857 | + .append( |
| 4858 | + $( '<div />' ).addClass( 'suggestions-special' ) |
| 4859 | + // Can't use click() because the container div is hidden when the textbox loses focus. Instead, |
| 4860 | + // listen for a mousedown followed by a mouseup on the same div |
| 4861 | + .mousedown( function( e ) { |
| 4862 | + context.data.mouseDownOn = $( e.target ).closest( '.suggestions-special' ); |
| 4863 | + } ) |
| 4864 | + .mouseup( function( e ) { |
| 4865 | + var $special = $( e.target ).closest( '.suggestions-special' ); |
| 4866 | + var $other = context.data.mouseDownOn; |
| 4867 | + context.data.mouseDownOn = $( [] ); |
| 4868 | + if ( $special.get( 0 ) != $other.get( 0 ) ) { |
| 4869 | + return; |
| 4870 | + } |
| 4871 | + context.data.$container.hide(); |
| 4872 | + if ( typeof context.config.special.select == 'function' ) { |
| 4873 | + context.config.special.select.call( $special, context.data.$textbox ); |
| 4874 | + } |
| 4875 | + context.data.$textbox.focus(); |
| 4876 | + } ) |
| 4877 | + .mousemove( function( e ) { |
| 4878 | + context.data.selectedWithMouse = true; |
| 4879 | + $.suggestions.highlight( |
| 4880 | + context, $( e.target ).closest( '.suggestions-special' ), false |
| 4881 | + ); |
| 4882 | + } ) |
| 4883 | + ) |
| 4884 | + .appendTo( $( 'body' ) ); |
| 4885 | + $(this) |
| 4886 | + // Stop browser autocomplete from interfering |
| 4887 | + .attr( 'autocomplete', 'off') |
| 4888 | + .keydown( function( e ) { |
| 4889 | + // Store key pressed to handle later |
| 4890 | + context.data.keypressed = ( e.keyCode == undefined ) ? e.which : e.keyCode; |
| 4891 | + context.data.keypressedCount = 0; |
| 4892 | + |
| 4893 | + switch ( context.data.keypressed ) { |
| 4894 | + // This preventDefault logic is duplicated from |
| 4895 | + // $.suggestions.keypress(), which sucks |
| 4896 | + case 40: |
| 4897 | + e.preventDefault(); |
| 4898 | + e.stopImmediatePropagation(); |
| 4899 | + break; |
| 4900 | + case 38: |
| 4901 | + case 27: |
| 4902 | + case 13: |
| 4903 | + if ( context.data.$container.is( ':visible' ) ) { |
| 4904 | + e.preventDefault(); |
| 4905 | + e.stopImmediatePropagation(); |
| 4906 | + } |
| 4907 | + } |
| 4908 | + } ) |
| 4909 | + .keypress( function( e ) { |
| 4910 | + context.data.keypressedCount++; |
| 4911 | + $.suggestions.keypress( e, context, context.data.keypressed ); |
| 4912 | + } ) |
| 4913 | + .keyup( function( e ) { |
| 4914 | + // Some browsers won't throw keypress() for arrow keys. If we got a keydown and a keyup without a |
| 4915 | + // keypress in between, solve it |
| 4916 | + if ( context.data.keypressedCount == 0 ) { |
| 4917 | + $.suggestions.keypress( e, context, context.data.keypressed ); |
| 4918 | + } |
| 4919 | + } ) |
| 4920 | + .blur( function() { |
| 4921 | + // When losing focus because of a mousedown |
| 4922 | + // on a suggestion, don't hide the suggestions |
| 4923 | + if ( context.data.mouseDownOn.length > 0 ) { |
| 4924 | + return; |
| 4925 | + } |
| 4926 | + context.data.$container.hide(); |
| 4927 | + $.suggestions.cancel( context ); |
| 4928 | + } ); |
| 4929 | + } |
| 4930 | + // Store the context for next time |
| 4931 | + $(this).data( 'suggestions-context', context ); |
| 4932 | + } ); |
| 4933 | + return returnValue !== null ? returnValue : $(this); |
| 4934 | +}; |
| 4935 | + |
| 4936 | +} )( jQuery ); |
4416 | 4937 | /** |
4417 | 4938 | * Simple predictive typing category adder for Mediawiki. |
4418 | 4939 | * Relies on globals: wgScriptPath, wgNamespaceIds, wgFormattedNamespaces |
Index: trunk/extensions/UploadWizard/resources/combined.min.js |
— | — | @@ -4417,6 +4417,527 @@ |
4418 | 4418 | |
4419 | 4419 | |
4420 | 4420 | |
| 4421 | + |
| 4422 | + |
| 4423 | + |
| 4424 | + |
| 4425 | + |
| 4426 | + |
| 4427 | + |
| 4428 | + |
| 4429 | + |
| 4430 | + |
| 4431 | + |
| 4432 | + |
| 4433 | + |
| 4434 | + |
| 4435 | + |
| 4436 | + |
| 4437 | + |
| 4438 | + |
| 4439 | + |
| 4440 | + |
| 4441 | + |
| 4442 | + |
| 4443 | + |
| 4444 | + |
| 4445 | + |
| 4446 | + |
| 4447 | + |
| 4448 | + |
| 4449 | + |
| 4450 | + |
| 4451 | + |
| 4452 | + |
| 4453 | + |
| 4454 | + |
| 4455 | + |
| 4456 | + |
| 4457 | + |
| 4458 | + |
| 4459 | +(function($){ |
| 4460 | + |
| 4461 | +$.suggestions={ |
| 4462 | + |
| 4463 | + |
| 4464 | + |
| 4465 | + |
| 4466 | +cancel:function(context){ |
| 4467 | +if(context.data.timerID!=null){ |
| 4468 | +clearTimeout(context.data.timerID); |
| 4469 | +} |
| 4470 | +if(typeof context.config.cancel=='function'){ |
| 4471 | +context.config.cancel.call(context.data.$textbox); |
| 4472 | +} |
| 4473 | +}, |
| 4474 | + |
| 4475 | + |
| 4476 | + |
| 4477 | + |
| 4478 | + |
| 4479 | +restore:function(context){ |
| 4480 | +context.data.$textbox.val(context.data.prevText); |
| 4481 | +}, |
| 4482 | + |
| 4483 | + |
| 4484 | + |
| 4485 | + |
| 4486 | + |
| 4487 | + |
| 4488 | +update:function(context,delayed){ |
| 4489 | + |
| 4490 | +function maybeFetch(){ |
| 4491 | +if(context.data.$textbox.val()!==context.data.prevText){ |
| 4492 | +context.data.prevText=context.data.$textbox.val(); |
| 4493 | +if(typeof context.config.fetch=='function'){ |
| 4494 | +context.config.fetch.call(context.data.$textbox,context.data.$textbox.val()); |
| 4495 | +} |
| 4496 | +} |
| 4497 | +} |
| 4498 | + |
| 4499 | +if(context.data.timerID!=null){ |
| 4500 | +clearTimeout(context.data.timerID); |
| 4501 | +} |
| 4502 | +if(delayed){ |
| 4503 | + |
| 4504 | +context.data.timerID=setTimeout(maybeFetch,context.config.delay); |
| 4505 | +}else{ |
| 4506 | +maybeFetch(); |
| 4507 | +} |
| 4508 | +$.suggestions.special(context); |
| 4509 | +}, |
| 4510 | +special:function(context){ |
| 4511 | + |
| 4512 | +if(typeof context.config.special.render=='function'){ |
| 4513 | + |
| 4514 | +setTimeout(function(){ |
| 4515 | + |
| 4516 | +$special=context.data.$container.find('.suggestions-special'); |
| 4517 | +context.config.special.render.call($special,context.data.$textbox.val()); |
| 4518 | +},1); |
| 4519 | +} |
| 4520 | +}, |
| 4521 | + |
| 4522 | + |
| 4523 | + |
| 4524 | + |
| 4525 | + |
| 4526 | +configure:function(context,property,value){ |
| 4527 | + |
| 4528 | +switch(property){ |
| 4529 | +case'fetch': |
| 4530 | +case'cancel': |
| 4531 | +case'special': |
| 4532 | +case'result': |
| 4533 | +case'$region': |
| 4534 | +context.config[property]=value; |
| 4535 | +break; |
| 4536 | +case'suggestions': |
| 4537 | +context.config[property]=value; |
| 4538 | + |
| 4539 | +if(typeof context.data!=='undefined'){ |
| 4540 | +if(context.data.$textbox.val().length==0){ |
| 4541 | + |
| 4542 | +context.data.$container.hide(); |
| 4543 | +}else{ |
| 4544 | + |
| 4545 | +context.data.$container.show(); |
| 4546 | + |
| 4547 | +var newCSS={ |
| 4548 | +'top':context.config.$region.offset().top+context.config.$region.outerHeight(), |
| 4549 | +'bottom':'auto', |
| 4550 | +'width':context.config.$region.outerWidth(), |
| 4551 | +'height':'auto' |
| 4552 | +} |
| 4553 | +if(context.config.positionFromLeft){ |
| 4554 | +newCSS['left']=context.config.$region.offset().left; |
| 4555 | +newCSS['right']='auto'; |
| 4556 | +}else{ |
| 4557 | +newCSS['left']='auto'; |
| 4558 | +newCSS['right']=$('body').width()-(context.config.$region.offset().left+context.config.$region.outerWidth()); |
| 4559 | +} |
| 4560 | +context.data.$container.css(newCSS); |
| 4561 | +var $results=context.data.$container.children('.suggestions-results'); |
| 4562 | +$results.empty(); |
| 4563 | +var expWidth=-1; |
| 4564 | +var $autoEllipseMe=$([]); |
| 4565 | +var matchedText=null; |
| 4566 | +for(var i=0;i<context.config.suggestions.length;i++){ |
| 4567 | +var text=context.config.suggestions[i]; |
| 4568 | +var $result=$('<div />') |
| 4569 | +.addClass('suggestions-result') |
| 4570 | +.attr('rel',i) |
| 4571 | +.data('text',context.config.suggestions[i]) |
| 4572 | +.mousemove(function(e){ |
| 4573 | +context.data.selectedWithMouse=true; |
| 4574 | +$.suggestions.highlight( |
| 4575 | +context,$(this).closest('.suggestions-results div'),false |
| 4576 | +); |
| 4577 | +}) |
| 4578 | +.appendTo($results); |
| 4579 | + |
| 4580 | +if(typeof context.config.result.render=='function'){ |
| 4581 | +context.config.result.render.call($result,context.config.suggestions[i]); |
| 4582 | +}else{ |
| 4583 | + |
| 4584 | +if(context.config.highlightInput){ |
| 4585 | +matchedText=context.data.prevText; |
| 4586 | +} |
| 4587 | +$result.append($('<span />') |
| 4588 | +.css('whiteSpace','nowrap') |
| 4589 | +.text(text) |
| 4590 | +); |
| 4591 | + |
| 4592 | + |
| 4593 | + |
| 4594 | +var $span=$result.children('span'); |
| 4595 | +if($span.outerWidth()>$result.width()&&$span.outerWidth()>expWidth){ |
| 4596 | + |
| 4597 | +expWidth=$span.outerWidth()+(context.data.$container.width()-$span.parent().width()); |
| 4598 | +} |
| 4599 | +$autoEllipseMe=$autoEllipseMe.add($result); |
| 4600 | +} |
| 4601 | +} |
| 4602 | + |
| 4603 | +if(expWidth>context.data.$container.width()){ |
| 4604 | +var maxWidth=context.config.maxExpandFactor*context.data.$textbox.width(); |
| 4605 | +context.data.$container.width(Math.min(expWidth,maxWidth)); |
| 4606 | +} |
| 4607 | + |
| 4608 | +$autoEllipseMe.autoEllipsis({hasSpan:true,tooltip:true,matchText:matchedText}); |
| 4609 | +} |
| 4610 | +} |
| 4611 | +break; |
| 4612 | +case'maxRows': |
| 4613 | +context.config[property]=Math.max(1,Math.min(100,value)); |
| 4614 | +break; |
| 4615 | +case'delay': |
| 4616 | +context.config[property]=Math.max(0,Math.min(1200,value)); |
| 4617 | +break; |
| 4618 | +case'maxExpandFactor': |
| 4619 | +context.config[property]=Math.max(1,value); |
| 4620 | +break; |
| 4621 | +case'submitOnClick': |
| 4622 | +case'positionFromLeft': |
| 4623 | +case'highlightInput': |
| 4624 | +context.config[property]=value?true:false; |
| 4625 | +break; |
| 4626 | +} |
| 4627 | +}, |
| 4628 | + |
| 4629 | + |
| 4630 | + |
| 4631 | + |
| 4632 | + |
| 4633 | +highlight:function(context,result,updateTextbox){ |
| 4634 | +var selected=context.data.$container.find('.suggestions-result-current'); |
| 4635 | +if(!result.get||selected.get(0)!=result.get(0)){ |
| 4636 | +if(result=='prev'){ |
| 4637 | +if(selected.is('.suggestions-special')){ |
| 4638 | +result=context.data.$container.find('.suggestions-result:last') |
| 4639 | +}else{ |
| 4640 | +result=selected.prev(); |
| 4641 | +if(selected.length==0){ |
| 4642 | + |
| 4643 | +if(context.data.$container.find('.suggestions-special').html()!=""){ |
| 4644 | +result=context.data.$container.find('.suggestions-special'); |
| 4645 | +}else{ |
| 4646 | +result=context.data.$container.find('.suggestions-results div:last'); |
| 4647 | +} |
| 4648 | +} |
| 4649 | +} |
| 4650 | +}else if(result=='next'){ |
| 4651 | +if(selected.length==0){ |
| 4652 | + |
| 4653 | +result=context.data.$container.find('.suggestions-results div:first'); |
| 4654 | +if(result.length==0&&context.data.$container.find('.suggestions-special').html()!=""){ |
| 4655 | + |
| 4656 | +result=context.data.$container.find('.suggestions-special'); |
| 4657 | +} |
| 4658 | +}else{ |
| 4659 | +result=selected.next(); |
| 4660 | +if(selected.is('.suggestions-special')){ |
| 4661 | +result=$([]); |
| 4662 | +}else if( |
| 4663 | +result.length==0&& |
| 4664 | +context.data.$container.find('.suggestions-special').html()!="" |
| 4665 | +){ |
| 4666 | + |
| 4667 | +result=context.data.$container.find('.suggestions-special'); |
| 4668 | +} |
| 4669 | +} |
| 4670 | +} |
| 4671 | +selected.removeClass('suggestions-result-current'); |
| 4672 | +result.addClass('suggestions-result-current'); |
| 4673 | +} |
| 4674 | +if(updateTextbox){ |
| 4675 | +if(result.length==0||result.is('.suggestions-special')){ |
| 4676 | +$.suggestions.restore(context); |
| 4677 | +}else{ |
| 4678 | +context.data.$textbox.val(result.data('text')); |
| 4679 | + |
| 4680 | + |
| 4681 | +context.data.$textbox.change(); |
| 4682 | +} |
| 4683 | +context.data.$textbox.trigger('change'); |
| 4684 | +} |
| 4685 | +}, |
| 4686 | + |
| 4687 | + |
| 4688 | + |
| 4689 | + |
| 4690 | +keypress:function(e,context,key){ |
| 4691 | +var wasVisible=context.data.$container.is(':visible'); |
| 4692 | +var preventDefault=false; |
| 4693 | +switch(key){ |
| 4694 | + |
| 4695 | +case 40: |
| 4696 | +if(wasVisible){ |
| 4697 | +$.suggestions.highlight(context,'next',true); |
| 4698 | +context.data.selectedWithMouse=false; |
| 4699 | +}else{ |
| 4700 | +$.suggestions.update(context,false); |
| 4701 | +} |
| 4702 | +preventDefault=true; |
| 4703 | +break; |
| 4704 | + |
| 4705 | +case 38: |
| 4706 | +if(wasVisible){ |
| 4707 | +$.suggestions.highlight(context,'prev',true); |
| 4708 | +context.data.selectedWithMouse=false; |
| 4709 | +} |
| 4710 | +preventDefault=wasVisible; |
| 4711 | +break; |
| 4712 | + |
| 4713 | +case 27: |
| 4714 | +context.data.$container.hide(); |
| 4715 | +$.suggestions.restore(context); |
| 4716 | +$.suggestions.cancel(context); |
| 4717 | +context.data.$textbox.trigger('change'); |
| 4718 | +preventDefault=wasVisible; |
| 4719 | +break; |
| 4720 | + |
| 4721 | +case 13: |
| 4722 | +context.data.$container.hide(); |
| 4723 | +preventDefault=wasVisible; |
| 4724 | +selected=context.data.$container.find('.suggestions-result-current'); |
| 4725 | +if(selected.size()==0||context.data.selectedWithMouse){ |
| 4726 | + |
| 4727 | + |
| 4728 | +$.suggestions.cancel(context); |
| 4729 | +context.config.$region.closest('form').submit(); |
| 4730 | +}else if(selected.is('.suggestions-special')){ |
| 4731 | +if(typeof context.config.special.select=='function'){ |
| 4732 | +context.config.special.select.call(selected,context.data.$textbox); |
| 4733 | +} |
| 4734 | +}else{ |
| 4735 | +if(typeof context.config.result.select=='function'){ |
| 4736 | +$.suggestions.highlight(context,selected,true); |
| 4737 | +context.config.result.select.call(selected,context.data.$textbox); |
| 4738 | +}else{ |
| 4739 | +$.suggestions.highlight(context,selected,true); |
| 4740 | +} |
| 4741 | +} |
| 4742 | +break; |
| 4743 | +default: |
| 4744 | +$.suggestions.update(context,true); |
| 4745 | +break; |
| 4746 | +} |
| 4747 | +if(preventDefault){ |
| 4748 | +e.preventDefault(); |
| 4749 | +e.stopImmediatePropagation(); |
| 4750 | +} |
| 4751 | +} |
| 4752 | +}; |
| 4753 | +$.fn.suggestions=function(){ |
| 4754 | + |
| 4755 | + |
| 4756 | +var returnValue=null; |
| 4757 | +var args=arguments; |
| 4758 | + |
| 4759 | +$(this).each(function(){ |
| 4760 | + |
| 4761 | + |
| 4762 | + |
| 4763 | +var context=$(this).data('suggestions-context'); |
| 4764 | +if(typeof context=='undefined'||context==null){ |
| 4765 | +context={ |
| 4766 | +config:{ |
| 4767 | +'fetch':function(){}, |
| 4768 | +'cancel':function(){}, |
| 4769 | +'special':{}, |
| 4770 | +'result':{}, |
| 4771 | +'$region':$(this), |
| 4772 | +'suggestions':[], |
| 4773 | +'maxRows':7, |
| 4774 | +'delay':120, |
| 4775 | +'submitOnClick':false, |
| 4776 | +'maxExpandFactor':3, |
| 4777 | +'positionFromLeft':true, |
| 4778 | +'highlightInput':false |
| 4779 | +} |
| 4780 | +}; |
| 4781 | +} |
| 4782 | + |
| 4783 | + |
| 4784 | + |
| 4785 | + |
| 4786 | +if(args.length>0){ |
| 4787 | +if(typeof args[0]=='object'){ |
| 4788 | + |
| 4789 | +for(var key in args[0]){ |
| 4790 | +$.suggestions.configure(context,key,args[0][key]); |
| 4791 | +} |
| 4792 | +}else if(typeof args[0]=='string'){ |
| 4793 | +if(args.length>1){ |
| 4794 | + |
| 4795 | +$.suggestions.configure(context,args[0],args[1]); |
| 4796 | +}else if(returnValue==null){ |
| 4797 | + |
| 4798 | +returnValue=(args[0]in context.config?undefined:context.config[args[0]]); |
| 4799 | +} |
| 4800 | +} |
| 4801 | +} |
| 4802 | + |
| 4803 | + |
| 4804 | + |
| 4805 | +if(typeof context.data=='undefined'){ |
| 4806 | +context.data={ |
| 4807 | + |
| 4808 | +'timerID':null, |
| 4809 | + |
| 4810 | +'prevText':null, |
| 4811 | + |
| 4812 | +'visibleResults':0, |
| 4813 | + |
| 4814 | +'mouseDownOn':$([]), |
| 4815 | +'$textbox':$(this), |
| 4816 | +'selectedWithMouse':false |
| 4817 | +}; |
| 4818 | + |
| 4819 | +var newCSS={ |
| 4820 | +'top':Math.round(context.data.$textbox.offset().top+context.data.$textbox.outerHeight()), |
| 4821 | +'width':context.data.$textbox.outerWidth(), |
| 4822 | +'display':'none' |
| 4823 | +} |
| 4824 | +if(context.config.positionFromLeft){ |
| 4825 | +newCSS['left']=context.config.$region.offset().left; |
| 4826 | +newCSS['right']='auto'; |
| 4827 | +}else{ |
| 4828 | +newCSS['left']='auto'; |
| 4829 | +newCSS['right']=$('body').width()-(context.config.$region.offset().left+context.config.$region.outerWidth()); |
| 4830 | +} |
| 4831 | + |
| 4832 | +context.data.$container=$('<div />') |
| 4833 | +.css(newCSS) |
| 4834 | +.addClass('suggestions') |
| 4835 | +.append( |
| 4836 | +$('<div />').addClass('suggestions-results') |
| 4837 | + |
| 4838 | + |
| 4839 | +.mousedown(function(e){ |
| 4840 | +context.data.mouseDownOn=$(e.target).closest('.suggestions-results div'); |
| 4841 | +}) |
| 4842 | +.mouseup(function(e){ |
| 4843 | +var $result=$(e.target).closest('.suggestions-results div'); |
| 4844 | +var $other=context.data.mouseDownOn; |
| 4845 | +context.data.mouseDownOn=$([]); |
| 4846 | +if($result.get(0)!=$other.get(0)){ |
| 4847 | +return; |
| 4848 | +} |
| 4849 | +$.suggestions.highlight(context,$result,true); |
| 4850 | +context.data.$container.hide(); |
| 4851 | +if(typeof context.config.result.select=='function'){ |
| 4852 | +context.config.result.select.call($result,context.data.$textbox); |
| 4853 | +} |
| 4854 | +context.data.$textbox.focus(); |
| 4855 | +}) |
| 4856 | +) |
| 4857 | +.append( |
| 4858 | +$('<div />').addClass('suggestions-special') |
| 4859 | + |
| 4860 | + |
| 4861 | +.mousedown(function(e){ |
| 4862 | +context.data.mouseDownOn=$(e.target).closest('.suggestions-special'); |
| 4863 | +}) |
| 4864 | +.mouseup(function(e){ |
| 4865 | +var $special=$(e.target).closest('.suggestions-special'); |
| 4866 | +var $other=context.data.mouseDownOn; |
| 4867 | +context.data.mouseDownOn=$([]); |
| 4868 | +if($special.get(0)!=$other.get(0)){ |
| 4869 | +return; |
| 4870 | +} |
| 4871 | +context.data.$container.hide(); |
| 4872 | +if(typeof context.config.special.select=='function'){ |
| 4873 | +context.config.special.select.call($special,context.data.$textbox); |
| 4874 | +} |
| 4875 | +context.data.$textbox.focus(); |
| 4876 | +}) |
| 4877 | +.mousemove(function(e){ |
| 4878 | +context.data.selectedWithMouse=true; |
| 4879 | +$.suggestions.highlight( |
| 4880 | +context,$(e.target).closest('.suggestions-special'),false |
| 4881 | +); |
| 4882 | +}) |
| 4883 | +) |
| 4884 | +.appendTo($('body')); |
| 4885 | +$(this) |
| 4886 | + |
| 4887 | +.attr('autocomplete','off') |
| 4888 | +.keydown(function(e){ |
| 4889 | + |
| 4890 | +context.data.keypressed=(e.keyCode==undefined)?e.which:e.keyCode; |
| 4891 | +context.data.keypressedCount=0; |
| 4892 | + |
| 4893 | +switch(context.data.keypressed){ |
| 4894 | + |
| 4895 | + |
| 4896 | +case 40: |
| 4897 | +e.preventDefault(); |
| 4898 | +e.stopImmediatePropagation(); |
| 4899 | +break; |
| 4900 | +case 38: |
| 4901 | +case 27: |
| 4902 | +case 13: |
| 4903 | +if(context.data.$container.is(':visible')){ |
| 4904 | +e.preventDefault(); |
| 4905 | +e.stopImmediatePropagation(); |
| 4906 | +} |
| 4907 | +} |
| 4908 | +}) |
| 4909 | +.keypress(function(e){ |
| 4910 | +context.data.keypressedCount++; |
| 4911 | +$.suggestions.keypress(e,context,context.data.keypressed); |
| 4912 | +}) |
| 4913 | +.keyup(function(e){ |
| 4914 | + |
| 4915 | + |
| 4916 | +if(context.data.keypressedCount==0){ |
| 4917 | +$.suggestions.keypress(e,context,context.data.keypressed); |
| 4918 | +} |
| 4919 | +}) |
| 4920 | +.blur(function(){ |
| 4921 | + |
| 4922 | + |
| 4923 | +if(context.data.mouseDownOn.length>0){ |
| 4924 | +return; |
| 4925 | +} |
| 4926 | +context.data.$container.hide(); |
| 4927 | +$.suggestions.cancel(context); |
| 4928 | +}); |
| 4929 | +} |
| 4930 | + |
| 4931 | +$(this).data('suggestions-context',context); |
| 4932 | +}); |
| 4933 | +return returnValue!==null?returnValue:$(this); |
| 4934 | +}; |
| 4935 | + |
| 4936 | +})(jQuery); |
| 4937 | + |
| 4938 | + |
| 4939 | + |
| 4940 | + |
| 4941 | + |
4421 | 4942 | (function($j){$j.fn.mwCoolCats=function(options){ |
4422 | 4943 | |
4423 | 4944 | var defaults={ |