1/* This file is part of cbautocomplete2. 2 * Copyright © 2010-2013 stiftung kulturserver.de ggmbh <[email protected]> 3 * 4 * cbui is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * cbui is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with cbui. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18/** 19 * jQuery .autoComplete() 20 * 21 * Transforms an <input/> into an autoComplete box 22 * TODOs 23 * 1) scroll when using arrow keys and marked element is not visible 24 * 2) fix ie6 style issues 25 * 26 * @Author Andre Mohren 27 */ 28jQuery.fn.autoComplete = function(params) { 29 30 /** 31 * Predefined parameters with their default value 32 */ 33 var options = { 34 /** 35 * How long has the entered string to be before we start to search? 36 */ 37 'minimumKeys': '2', 38 /** 39 * How long to wait after pressing a key for start searching? 40 */ 41 'waitTime': '1000', 42 /** 43 * Is the <inpout/> value already validated at loading time? 44 */ 45 'validated': false, 46 /** 47 *Is the initial <inpout/> value already validated at loading time? 48 */ 49 initValidated : false, 50 /** 51 * Which url will be called to get out completion data? 52 */ 53 'requestUrl': '/module/jscript/lib/jquery_plugins/autocomplete2/exampleAjax.php', 54 /** 55 * Should we close the box and reset the <input/> when its losing focus? 56 */ 57 'closeWhenBlur': true, 58 /** 59 * Name of the search-param which will be send as _GET to PHP. 60 */ 61 'requestParam': 'actualInput', 62 /** 63 * Are not validated values allowed? 64 */ 65 'allowFreetext': false, 66 /** 67 * Where do we put the selected id? 68 */ 69 'putIdInto': '', 70 /** 71 * Custom callback for click? 72 */ 73 'customCallback': false, 74 /** 75 * Custom callback final for click? 76 */ 77 'customCallbackFinal': false, 78 /** 79 * Header 80 */ 81 'header': false, 82 /** 83 * Footer 84 */ 85 'footer': false, 86 /** 87 * Free text entry in footer 88 */ 89 'footerFreetext' : false, 90 /** 91 * Description line in case of free text entry in footer 92 */ 93 'footerMissingText' : '', 94 /** 95 * Info line in case of free text entry in footer 96 */ 97 'footerInfoText' : '', 98 /** 99 * Text to display when there's no results 100 */ 101 'noResultsText' : '', 102 /** 103 * Text for the button that is going to be placed in the footer 104 */ 105 'button' : false, 106 /** 107 * Provide a callback for buttonb click 108 */ 109 'onButtonClick' : false, 110 /** 111 * Trap "enter" keypress to avoid submitting forms? 112 */ 113 'trapEnter': false, 114 /** 115 * Provide a callback for showbox 116 */ 117 'onShow' : false, 118 /** 119 * is some other value for filtering required, store to extraParams dynamical 120 */ 121 'dynamicFilterValue': { 122 'urlAddon': '', 123 'valueFrom': '' 124 } 125 }; 126 127 /* 128 * All other parameters are passed on as extra request parameters 129 */ 130 var extra = {}; 131 for(var param in params) { 132 if (options[param] != undefined) { 133 options[param] = params[param]; 134 } else { 135 extra[param] = params[param]; 136 } 137 } 138 options.extraParams = extra; 139 140 jQuery(this).each(function() { 141 //ToDo: save old blur function for later useage 142 jQuery(this).unbind('blur'); 143 //ToDo: bind only <input/> 144 //ToDo: compare parameters with old box 145 //ToDo: icon not over border 146 //ToDo: scroll when using key-selecting 147 148 /** 149 * Our <input/> 150 */ 151 var element = this; 152 /** 153 * Used for saving the timeout 154 */ 155 var timeout = ''; 156 /** 157 * HTML Code for the box 158 */ 159 var box = jQuery('<div class="__AC_position"><div class="__AC_close"></div><div class="__AC_layer"></div></div>');
160 var validatingBox = jQuery('<div class="__AC_position"><div class="__AC_layer __AC_validating"></div></div>'); 161 162 /** 163 * The default value of the box 164 */ 165 var def = jQuery(element).val(); 166 /** 167 * Used for temporary saving what we have typed into the box 168 */ 169 var typed = ''; 170 /** 171 * Is the box open at the moment? 172 */ 173 var boxShown = false; 174 /** 175 * Do we mouseover an entry at the moment? 176 */ 177 var hoverEntry = false; 178 /** 179 * The default state of the box 180 */ 181 var isValidated = options['validated']; 182 /** 183 * The proofed default state of the box 184 */ 185 var initValidated = options['initValidated']; 186 /** 187 * Has the box focus? 188 */ 189 var hasFocus = false; 190 /** 191 * Is our last saved value freetext? 192 */ 193 var isFreetext = false; 194 /** 195 * The original value 196 */ 197 var originalVal = jQuery(element).val(); 198 199 /** 200 * Display the validated state 201 */ 202 var validated = function() { 203 jQuery(element).removeClass('__AC_invalidated __AC_validating __AC_editing __AC_freetext') 204 .addClass('__AC_validated'); 205 validatingBox.remove(); 206 }; 207 208 /** 209 * Display the invalidated state 210 */ 211 var invalidated = function() { 212 jQuery(element).removeClass('__AC_validated __AC_validating __AC_editing __AC_freetext') 213 .addClass('__AC_invalidated'); 214 validatingBox.remove(); 215 }; 216 217 /** 218 * Display the working state 219 */ 220 var validating = function() { 221 jQuery(element).removeClass('__AC_validated __AC_invalidated __AC_editing __AC_freetext') 222 .addClass('__AC_validating'); 223 validatingBox.remove(); 224 }; 225 226 /** 227 * Display the editing state 228 */ 229 var editing = function() { 230 jQuery(element).removeClass('__AC_validated __AC_invalidated __AC_validating __AC_freetext') 231 .addClass('__AC_editing'); 232 233 jQuery(element).one('keypress', function(e) { 234 if (!boxShown && e.which != 13 && e.which != 27) { 235 showValidatingBox(); 236 } 237 }); 238 }; 239 240 /** 241 * Display the freetext state 242 */ 243 var freetext = function() { 244 jQuery(element).removeClass('__AC_validated __AC_invalidated __AC_validating __AC_editing') 245 .addClass('__AC_freetext'); 246 validatingBox.remove(); 247 }; 248 249 var selectEntry = function() { 250 var val = jQuery(this).attr('data-ac-value'); 251 jQuery(element).val(val); 252 validated(); 253 hideBox(); 254 hoverEntry = false; 255 typed = def = val; 256 isFreetext = false; 257 if (options['putIdInto'] != "") { 258 jQuery(options['putIdInto']).val(jQuery(this).attr('id')).change(); 259 } 260 if (options['customCallbackFinal']) { 261 options['customCallbackFinal']({ 262 'Element': jQuery(element), 263 'Id' : jQuery(this).attr('id'), 264 'Title' : val 265 }); 266 } 267 }; 268 269 /** 270 * Show box 271 */ 272 var showBox = function() { 273 if (hasFocus || !options['closeWhenBlur']) { 274 validating(); 275 boxShown = true; 276 options.extraParams[options.requestParam] = typed; 277 278 // get extraParams dynamical 279 if (options['dynamicFilterValue']['urlAddon'] != '' && options['dynamicFilterValue']['valueFrom'] != '') { 280 options.extraParams[options['dynamicFilterValue']['urlAddon']] = jQuery('#' + options['dynamicFilterValue']['valueFrom']).val(); 281 } 282 283 jQuery.getJSON( 284 options['requestUrl'], 285 options.extraParams, 286 function(JSONdata) { 287 if (hasFocus || !options['closeWhenBlur']) { 288 289 /* handle deprecated format: {data : [{ results : [...] }] */
290 if (JSONdata.data != undefined && JSONdata.results == undefined && JSONdata.data[0]) JSONdata = JSONdata.data[0]; 291 292 box.find('.__AC_layer').html('<div class="__AC_data"></div>'); 293 box.find('.__AC_close').click(function() { 294 hoverEntry = false; 295 jQuery(element).val('').blur(); 296 }).hover(function() { 297 hoverEntry = true; 298 }, function() { 299 hoverEntry = false; 300 } 301 ); 302 if (JSONdata['header'] != undefined) { 303 box.find('.__AC_layer').prepend(JSONdata['header']); 304 } else if (options['header']) { 305 box.find('.__AC_layer').prepend(jQuery(options['header']).clone(true)); 306 } 307 if (JSONdata['results'] != undefined) { 308 var position = box.find('.__AC_data'); 309 if (JSONdata['results'].length > 0) { 310 for (var record in JSONdata['results']) { 311 var callback = selectEntry; 312 if (typeof(JSONdata['results'][record]['customCallback']) !== 'undefined') { 313 callback = JSONdata['results'][record]['customCallback']; 314 } else if (options['customCallback']) { 315 callback = options['customCallback']; 316 } 317 318 jQuery('<div class="__AC_record" ' +
319 'title="' +JSONdata['results'][record]['value'] + ' | ' +JSONdata['results'][record]['id']+ '" ' + 320 'id="' +JSONdata['results'][record]['id'] + '" ' + 321 'data-ac-value="' +JSONdata['results'][record]['value'] + '">' + 322 JSONdata['results'][record]['info'] + '</div>') 323 .appendTo(position).click(callback).hover(function() { 324 jQuery(this).addClass('__AC_ie8HoverFix'); 325 hoverEntry = true; 326 jQuery('.__AC_keyhover').removeClass('__AC_keyhover'); 327 isFreetext = false; 328 }, function() { 329 jQuery(this).removeClass('__AC_ie8HoverFix'); 330 hoverEntry = false; 331 }); 332 } 333 } else { 334 box.find('.__AC_layer').append('<div>' + options['noResultsText'] + '</div>'); 335 } 336 } else { 337 box.find('.__AC_layer').append('<div>' + options['noResultsText'] + '</div>'); 338 } 339 340 var footerFreetext = options['footerFreetext'] || JSONdata['footerFreetext']; 341 var footerMissingText = options['footerMissingText'] || JSONdata['footerMissingText']; 342 var footerInfoText = options['footerInfoText'] || JSONdata['footerInfoText']; 343 if (JSONdata['footer'] != undefined) { 344 box.find('.__AC_layer').append(JSONdata['footer']); 345 } else if (options['footer']) { 346 box.find('.__AC_layer').append(jQuery(options['footer']).clone(true)); 347 } else if (footerFreetext) { 348 box.find('.__AC_layer').append(jQuery('<div class="__AC_button">' + footerMissingText + '</div>').click(function(){ 349 jQuery(element).val(""); 350 jQuery('<div><input class="autocomplete-new-city"/><button class="input-button-go __AC_button">go</button></div>').find("button").click(function(){ 351 var name = jQuery(this).parent().find("input").val(); 352 hideBox(); 353 hoverEntry = false; 354 if (name != "") { 355 freetext(); 356 isFreetext = true; 357 } else { 358 invalidated(); 359 isFreetext = false; 360 } 361 jQuery(element).val(name); 362 def = typed = name; 363 jQuery(element).blur(); 364 }).css({ 365 "display": "block", 366 "margin-left": parseInt(jQuery(".__AC_data").innerWidth()) - 70 + "px" 367 }).parent().find("input").keyup(function(e){ 368 if (e.which == 13) { 369 jQuery(this).parent().find("button").click(); 370 } 371 }).css({ 372 "width": parseInt(jQuery(".__AC_data").innerWidth()) - 90 + "px" 373 }).attr('placeholder', footerInfoText).focus(function() { 374 jQuery(this).unbind("focus").css({ 375 "color": "#000000" 376 }).val(""); 377 }).parent().hover(function() { 378 hoverEntry = true; 379 }, function() { 380 hoverEntry = false; 381 }).insertAfter(this); 382 jQuery(this).remove(); 383 jQuery(".__AC_data").remove(); 384 }).hover(function() { 385 hoverEntry = true; 386 }, function() { 387 hoverEntry = false; 388 }).css({ 389 "color": "#BB3A11", 390 "background-color": "#F9F5E1", 391 "margin-right": "10px", 392 "margin-top": "10px", 393 "padding": "2px",
394 "cursor": "pointer" 395 })); 396 } else if (options['button'] != undefined && typeof options['onButtonClick'] === 'function') { 397 box.find('.__AC_layer').append('<div class="__AC_button">' + options['button'] + '</div>'); 398 box.find('.__AC_button').click(function(e) { 399 options['onButtonClick'](e); 400 hideBox(); 401 }); 402 } 403 box.css('width', jQuery(element).outerWidth() + 'px') 404 jQuery(element).parent().append(box); 405 editing(); 406 if (options['onShow'] && typeof options['onShow'] === 'function') { 407 options['onShow'](); 408 } 409 } 410 } 411 ); 412 } 413 }; 414 415 /** 416 * Hide the box 417 */ 418 var hideBox = function() { 419 box.remove(); 420 boxShown = false; 421 }; 422 423 var showValidatingBox = function() { 424 validatingBox.appendTo(jQuery(element).parent()) 425 .css('width', jQuery(element).outerWidth() + 'px'); 426 } 427 428 /** 429 * Function which will be triggered when we focus the box. 430 */ 431 jQuery(element).focus(function() { 432 hasFocus = true; 433 if (!isValidated && originalVal == jQuery(element).val()) { 434 jQuery(element).val(''); 435 } 436 editing(); 437 }); 438 439 /** 440 * Leaving the box 441 */ 442 jQuery(element).blur(function() { 443 hasFocus = false; 444 if (jQuery(element).val() == '') { 445 hideBox(); 446 jQuery(element).val(originalVal); 447 if (isValidated) { 448 validated(); 449 } else { 450 invalidated(); 451 } 452 } else if (options['allowFreetext']) { 453 hideBox(); 454 freetext(); 455 } else if (!hoverEntry && options['closeWhenBlur']) { 456 hideBox(); 457 if (def != jQuery(element).val()) { 458 jQuery(element).val(def); 459 } 460 if (isFreetext) { 461 freetext(); 462 } else if (isValidated || def == typed) { 463 validated(); 464 } else { 465 jQuery(element).val(def); 466 invalidated(); 467 } 468 } else if (def == typed) { 469 if (isFreetext) { 470 freetext(); 471 } else { 472 validated(); 473 } 474 } 475 }); 476 477 478 /** 479 * Key enter, has to be done on keypress so that it happens before any submit events 480 */ 481 jQuery(element).keypress(function(keyCapture) { 482 if (keyCapture.which == '13') { 483 // simulate click to trigger correct callback (possibly from JSONdata) 484 jQuery('.__AC_keyhover').click(); 485 return !options.trapEnter; 486 } 487 return true; 488 }); 489 490 /** 491 * Pressing and button which the box has focus 492 */ 493 jQuery(element).keyup(function(keyCapture) { 494 clearTimeout(timeout); 495 typed = jQuery(element).val(); 496 /** 497 * Key escape 498 */ 499 if (keyCapture.which == 27) { 500 hideBox(); 501 jQuery(element).blur(); 502 /** 503 * Key up-arrow 504 */ 505 } else if (keyCapture.which == '38') { 506 if (jQuery('.__AC_keyhover').length == 0 || jQuery('.__AC_keyhover').prev().length == 0) { // || first 507 jQuery('.__AC_keyhover').removeClass('__AC_keyhover'); 508 jQuery('.__AC_data div:last-child').addClass('__AC_keyhover'); 509 } else { 510 jQuery('.__AC_keyhover').removeClass('__AC_keyhover').prev().addClass('__AC_keyhover'); 511 } 512 /** 513 * Key down-arrow 514 */ 515 } else if (keyCapture.which == '40') { 516 if (jQuery('.__AC_keyhover').length == 0 || jQuery('.__AC_keyhover').next().length == 0) { // || last 517 jQuery('.__AC_keyhover').removeClass('__AC_keyhover'); 518 jQuery('.__AC_data div:first-child').addClass('__AC_keyhover'); 519 } else { 520 jQuery('.__AC_keyhover').removeClass('__AC_keyhover').next().addClass('__AC_keyhover'); 521 } 522 } else if (typed.length < options['minimumKeys']) { 523 hideBox(); 524 //showValidatingBox(); 525 } else if (def != typed && typed.length >= options['minimumKeys']) { 526 hideBox(); 527 showValidatingBox(); 528 timeout = window.setTimeout(function() { 529 showBox(); 530 }, options['waitTime']); 531 } 532 }); 533 534 /** 535 * Do correct styling of the box at init 536 */ 537 if (isValidated || initValidated) { 538 validated(); 539 } else { 540 invalidated(); 541 } 542 543 }); 544};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.