1//@deprecated 3.4 2var Observer = new Class({ 3 Implements: [Options, Events], 4 options: { 5 periodical: false, 6 delay: 1000 7 }, 8 initialize: function (el, onFired, options) { 9 this.element = document.id(el) || $document.id(el); 10 this.addEvent('onFired', onFired); 11 this.setOptions(options); 12 this.bound = this.changed.bind(this); 13 this.resume(); 14 }, 15 changed: function () { 16 var value = this.element.get('value'); 17 if ($equals(this.value, value)) return; 18 this.clear(); 19 this.value = value; 20 this.timeout = this.onFired.delay(this.options.delay, this); 21 }, 22 setValue: function (value) { 23 this.value = value; 24 this.element.set('value', value); 25 return this.clear(); 26 }, 27 onFired: function () { 28 this.fireEvent('onFired', [this.value, this.element]); 29 }, 30 clear: function () { 31 clearTimeout(this.timeout || null); 32 return this; 33 }, 34 pause: function () { 35 if (this.timer) clearTimeout(this.timer); 36 else this.element.removeEvent('keyup', this.bound); 37 return this.clear(); 38 }, 39 resume: function () { 40 this.value = this.element.get('value'); 41 if (this.options.periodical) this.timer = this.changed.periodical(this.options.periodical, this); 42 else this.element.addEvent('keyup', this.bound); 43 return this; 44 } 45}); 46var $equals = function (obj1, obj2) { 47 return (obj1 == obj2 || JSON.encode(obj1) == JSON.encode(obj2)); 48 }; 49var Autocompleter = new Class({ 50 Implements: [Options, Events], 51 options: { 52 minLength: 1, 53 markQuery: true, 54 width: 'inherit', 55 maxChoices: 10, 56 injectChoice: null, 57 customChoices: null, 58 emptyChoices: null, 59 visibleChoices: true, 60 className: 'autocompleter-choices', 61 zIndex: 1000, 62 delay: 400, 63 observerOptions: {}, 64 fxOptions: {}, 65 autoSubmit: false, 66 overflow: false, 67 overflowMargin: 25, 68 selectFirst: false, 69 filter: null, 70 filterCase: false, 71 filterSubset: false, 72 forceSelect: false, 73 selectMode: true, 74 choicesMatch: null, 75 multiple: false, 76 separator: ', ', 77 separatorSplit: /\s*[,;]\s*/, 78 autoTrim: false, 79 allowDupes: false, 80 cache: true, 81 relative: false 82 }, 83 initialize: function (element, options) { 84 this.element = document.id(element); 85 this.setOptions(options); 86 this.build(); 87 this.observer = new Observer(this.element, this.prefetch.bind(this), Object.merge({}, { 88 'delay': this.options.delay 89 }, this.options.observerOptions)); 90 this.queryValue = null; 91 if (this.options.filter) this.filter = this.options.filter.bind(this); 92 var mode = this.options.selectMode; 93 this.typeAhead = (mode == 'type-ahead'); 94 this.selectMode = (mode === true) ? 'selection' : mode; 95 this.cached = []; 96 }, 97 build: function () { 98 if (document.id(this.options.customChoices)) { 99 this.choices = this.options.customChoices; 100 } else { 101 this.choices = new Element('ul', { 102 'class': this.options.className, 103 'styles': { 104 'zIndex': this.options.zIndex 105 } 106 }).inject(document.body); 107 this.relative = false; 108 if (this.options.relative) { 109 this.choices.inject(this.element, 'after'); 110 this.relative = this.element.getOffsetParent(); 111 } 112 this.fix = new OverlayFix(this.choices); 113 } 114 if (!this.options.separator.test(this.options.separatorSplit)) { 115 this.options.separatorSplit = this.options.separator; 116 } 117 this.fx = (!this.options.fxOptions) ? null : new Fx.Tween(this.choices, Ob
117ject.merge({}, { 118 'property': 'opacity', 119 'link': 'cancel', 120 'duration': 200 121 }, this.options.fxOptions)).addEvent('onStart', Chain.prototype.clearChain).set(0); 122 this.element.setProperty('autocomplete', 'off').addEvent((Browser.ie || Browser.safari || Browser.chrome) ? 'keydown' : 'keypress', this.onCommand.bind(this)).addEvent('click', this.onCommand.bind(this, [false])).addEvent('focus', this.toggleFocus.pass({ 123 bind: this, 124 arguments: true, 125 delay: 100 126 })).addEvent('blur', this.toggleFocus.pass({ 127 bind: this, 128 arguments: false, 129 delay: 100 130 })); 131 }, 132 destroy: function () { 133 if (this.fix) this.fix.destroy(); 134 this.choices = this.selected = this.choices.destroy(); 135 }, 136 toggleFocus: function (state) { 137 this.focussed = state; 138 if (!state) this.hideChoices(true); 139 this.fireEvent((state) ? 'onFocus' : 'onBlur', [this.element]); 140 }, 141 onCommand: function (e) { 142 if (!e && this.focussed) return this.prefetch(); 143 if (e && e.key && !e.shift) { 144 switch (e.key) { 145 case 'enter': 146 if (this.element.value != this.opted) return true; 147 if (this.selected && this.visible) { 148 this.choiceSelect(this.selected); 149 return !!(this.options.autoSubmit); 150 } 151 break; 152 case 'up': 153 case 'down': 154 if (!this.prefetch() && this.queryValue !== null) { 155 var up = (e.key == 'up'); 156 this.choiceOver((this.selected || this.choices)[(this.selected) ? ((up) ? 'getPrevious' : 'getNext') : ((up) ? 'getLast' : 'getFirst')](this.options.choicesMatch), true); 157 } 158 return false; 159 case 'esc': 160 case 'tab': 161 this.hideChoices(true); 162 break; 163 } 164 } 165 return true; 166 }, 167 setSelection: function (finish) { 168 var input = this.selected.inputValue, 169 value = input; 170 var start = this.queryValue.length, 171 end = input.length; 172 if (input.substr(0, start).toLowerCase() != this.queryValue.toLowerCase()) start = 0; 173 if (this.options.multiple) { 174 var split = this.options.separatorSplit; 175 value = this.element.value; 176 start += this.queryIndex; 177 end += this.queryIndex; 178 var old = value.substr(this.queryIndex).split(split, 1)[0]; 179 value = value.substr(0, this.queryIndex) + input + value.substr(this.queryIndex + old.length); 180 if (finish) { 181 var tokens = value.split(this.options.separatorSplit).filter(function (entry) { 182 return this.test(entry); 183 }, /[^\s,]+/); 184 if (!this.options.allowDupes) tokens = [].combine(tokens); 185 var sep = this.options.separator; 186 value = tokens.join(sep) + sep; 187 end = value.length; 188 } 189 } 190 this.observer.setValue(value); 191 this.opted = value; 192 if (finish || this.selectMode == 'pick') start = end; 193 this.element.selectRange(start, end); 194 this.fireEvent('onSelection', [this.element, this.selected, value, input]); 195 }, 196 showChoices: function () { 197 var match = this.options.choicesMatch, 198 first = this.choices.getFirst(match); 199 this.selected = this.selectedValue = null; 200 if (this.fix) { 201 var pos = this.element.getCoordinates(this.relative), 202 width = this.options.width || 'auto'; 203 this.choices.setStyles({ 204 'left': pos.left, 205 'top': pos.bottom, 206 'width': (width === true || width == 'inherit') ? pos.width : width 207 }); 208 } 209 if (!first) return; 210 if (!this.visible) { 211 this.visible = true; 212 this.choices.setStyle('display', ''); 213 if (this.fx) this.fx.start(1); 214 this.fireEvent('onShow', [this.element, this.choices]); 215 } 216 if (this.options.selectFirst || this.typeAhead || first.inputValue == this.queryValue) this.choiceOver(first, this.typeAhead); 217 var items = this.choices.getChildren(match), 218 max = this.options.maxChoices; 219 var styles = { 220 'overflowY': 'hidden', 221 'height': '' 222 }; 223 this.overflown = false; 224 if (items.length > max) { 225 var item = items[max - 1]; 226 styles.overflowY = 'scroll'; 227 styles.height = item.getCoordinates(this.choices).bottom; 228 this.overflown = true; 229 }; 230 this.choices.setStyles(styles); 231 this.fix.show(); 232 if (this.options.visibleChoices) { 233 var scroll = document.getScroll(), 234 size = document.getSize(), 235 coords = this.choices.getCoordinates(); 236 if (coords.right > scroll.x + size.x) scroll.x = coords.right - size.x; 237 if (coords.bottom > scroll.y + size.y) scroll.y = coords.bottom - size.y; 238 window.scrollTo(Math.min(scroll.x, coords.left), Math.min(scroll.y, coords.top)); 239 } 240 }, 241 // TODO: No $arguments in MT 1.3 242 hideChoices: function (clear) { 243 if (clear) { 244 var value = this.element.value; 245 if (this.options.forceSelect) value = this.opted; 246 if (this.options.autoTrim) { 247 value = value.split(this.options.separatorSplit).filter($arguments(0)).join(this.options.separator); 248 } 249 this.observer.setValue(value); 250 } 251 if (!this.visible) return; 252 this.visible = false;
253 if (this.selected) this.selected.removeClass('autocompleter-selected'); 254 this.observer.clear(); 255 var hide = function () { 256 this.choices.setStyle('display', 'none'); 257 this.fix.hide(); 258 }.bind(this); 259 if (this.fx) this.fx.start(0).chain(hide); 260 else hide(); 261 this.fireEvent('onHide', [this.element, this.choices]); 262 }, 263 prefetch: function () { 264 var value = this.element.value, 265 query = value; 266 if (this.options.multiple) { 267 var split = this.options.separatorSplit; 268 var values = value.split(split); 269 var index = this.element.getSelectedRange().start; 270 var toIndex = value.substr(0, index).split(split); 271 var last = toIndex.length - 1; 272 index -= toIndex[last].length; 273 query = values[last]; 274 } 275 if (query.length < this.options.minLength) { 276 this.hideChoices(); 277 } else { 278 if (query === this.queryValue || (this.visible && query == this.selectedValue)) { 279 if (this.visible) return false; 280 this.showChoices(); 281 } else { 282 this.queryValue = query; 283 this.queryIndex = index; 284 if (!this.fetchCached()) this.query(); 285 } 286 } 287 return true; 288 }, 289 fetchCached: function () { 290 return false; 291 if (!this.options.cache || !this.cached || !this.cached.length || this.cached.length >= this.options.maxChoices || this.queryValue) return false; 292 this.update(this.filter(this.cached)); 293 return true; 294 }, 295 update: function (tokens) { 296 this.choices.empty(); 297 this.cached = tokens; 298 var type = tokens && typeOf(tokens); 299 if (!type || (type == 'array' && !tokens.length) || (type == 'hash' && !tokens.getLength())) { 300 (this.options.emptyChoices || this.hideChoices).call(this); 301 } else { 302 if (this.options.maxChoices < tokens.length && !this.options.overflow) tokens.length = this.options.maxChoices; 303 tokens.each(this.options.injectChoice || 304 function (token) { 305 var choice = new Element('li', { 306 'html': this.markQueryValue(token) 307 }); 308 choice.inputValue = token; 309 this.addChoiceEvents(choice).inject(this.choices); 310 }, this); 311 this.showChoices(); 312 } 313 }, 314 choiceOver: function (choice, selection) { 315 if (!choice || choice == this.selected) return; 316 if (this.selected) this.selected.removeClass('autocompleter-selected'); 317 this.selected = choice.addClass('autocompleter-selected'); 318 this.fireEvent('onSelect', [this.element, this.selected, selection]); 319 if (!this.selectMode) this.opted = this.element.value; 320 if (!selection) return; 321 this.selectedValue = this.selected.inputValue; 322 if (this.overflown) { 323 var coords = this.selected.getCoordinates(this.choices), 324 margin = this.options.overflowMargin, 325 top = this.choices.scrollTop, 326 height = this.choices.offsetHeight, 327 bottom = top + height; 328 if (coords.top - margin < top && top) this.choices.scrollTop = Math.max(coords.top - margin, 0); 329 else if (coords.bottom + margin > bottom) this.choices.scrollTop = Math.min(coords.bottom - height + margin, bottom); 330 } 331 if (this.selectMode) this.setSelection(); 332 }, 333 choiceSelect: function (choice) { 334 if (choice) this.choiceOver(choice); 335 this.setSelection(true); 336 this.queryValue = false; 337 this.hideChoices(); 338 }, 339 filter: function (tokens) { 340 return (tokens || this.tokens).filter(function (token) { 341 return this.test(token); 342 }, new RegExp(((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp(), (this.options.filterCase) ? '' : 'i')); 343 }, 344 markQueryValue: function (str) { 345 return (!this.options.markQuery || !this.queryValue) ? str : str.replace(new RegExp('(' + ((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp() + ')', (this.options.filterCase) ? '' : 'i'), '<span class="autocompleter-queried">$1</span>'); 346 }, 347 addChoiceEvents: function (el) { 348 return el.addEvents({ 349 'mouseover': this.choiceOver.bind(this, el), 350 'click': this.choiceSelect.bind(this, el) 351 }); 352 } 353}); 354var OverlayFix = new Class({ 355 initialize: function (el) { 356 if (Browser.ie) { 357 this.element = document.id(el); 358 this.relative = this.element.getOffsetParent(); 359 this.fix = new Element('iframe', { 360 'frameborder': '0', 361 'scrolling': 'no', 362 'src': 'javascript:false;', 363 'styles': { 364 'position': 'absolute', 365 'border': 'none', 366 'display': 'none', 367 'filter': 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)' 368 } 369 }).inject(this.element, 'after'); 370 } 371 }, 372 show: function () { 373 if (this.fix) { 374 var coords = this.element.getCoordinates(this.relative); 375 delete coords.right; 376 delete coords.bottom; 377 this.fix.setStyles(Object.append(coords, {
378 'display': '', 379 'zIndex': (this.element.getStyle('zIndex') || 1) - 1 380 })); 381 } 382 return this; 383 }, 384 hide: function () { 385 if (this.fix) this.fix.setStyle('display', 'none'); 386 return this; 387 }, 388 destroy: function () { 389 if (this.fix) this.fix = this.fix.destroy(); 390 } 391}); 392Element.implement({ 393 getSelectedRange: function () { 394 if (!Browser.ie) return { 395 start: this.selectionStart, 396 end: this.selectionEnd 397 }; 398 var pos = { 399 start: 0, 400 end: 0 401 }; 402 var range = this.getDocument().selection.createRange(); 403 if (!range || range.parentElement() != this) return pos; 404 var dup = range.duplicate(); 405 if (this.type == 'text') { 406 pos.start = 0 - dup.moveStart('character', -100000); 407 pos.end = pos.start + range.text.length; 408 } else { 409 var value = this.value; 410 var offset = value.length - value.match(/[\n\r]*$/)[0].length; 411 dup.moveToElementText(this); 412 dup.setEndPoint('StartToEnd', range); 413 pos.end = offset - dup.text.length; 414 dup.setEndPoint('StartToStart', range); 415 pos.start = offset - dup.text.length; 416 } 417 return pos; 418 }, 419 selectRange: function (start, end) { 420 if (Browser.ie) { 421 var diff = this.value.substr(start, end - start).replace(/\r/g, '').length; 422 start = this.value.substr(0, start).replace(/\r/g, '').length; 423 var range = this.createTextRange(); 424 range.collapse(true); 425 range.moveEnd('character', start + diff); 426 range.moveStart('character', start); 427 range.select(); 428 } else { 429 this.focus(); 430 this.setSelectionRange(start, end); 431 } 432 return this; 433 } 434}); 435Autocompleter.Base = Autocompleter; 436Autocompleter.Request = new Class({ 437 Extends: Autocompleter, 438 options: { 439 postData: {}, 440 ajaxOptions: {}, 441 postVar: 'value' 442 }, 443 query: function () { 444 var data = this.options.postData.unlink || {}; 445 data[this.options.postVar] = this.queryValue; 446 var indicator = document.id(this.options.indicator); 447 if (indicator) indicator.setStyle('display', ''); 448 var cls = this.options.indicatorClass; 449 if (cls) this.element.addClass(cls); 450 this.fireEvent('onRequest', [this.element, this.request, data, this.queryValue]); 451 this.request.send({ 452 'data': data 453 }); 454 }, 455 queryResponse: function () { 456 var indicator = document.id(this.options.indicator); 457 if (indicator) indicator.setStyle('display', 'none'); 458 var cls = this.options.indicatorClass; 459 if (cls) this.element.removeClass(cls); 460 return this.fireEvent('onComplete', [this.element, this.request]); 461 } 462}); 463Autocompleter.Request.JSON = new Class({ 464 Extends: Autocompleter.Request, 465 initialize: function (el, url, options) { 466 this.parent(el, options); 467 this.request = new Request.JSON(Object.merge({}, { 468 'url': url, 469 'link': 'cancel' 470 }, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this)); 471 }, 472 queryResponse: function (response) { 473 this.parent(); 474 this.update(response); 475 } 476}); 477Autocompleter.Request.HTML = new Class({ 478 Extends: Autocompleter.Request, 479 initialize: function (el, url, options) { 480 this.parent(el, options); 481 this.request = new Request.HTML(Object.merge({}, { 482 'url': url, 483 'link': 'cancel', 484 'update': this.choices 485 }, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this)); 486 }, 487 queryResponse: function (tree, elements) { 488 this.parent(); 489 if (!elements || !elements.length) { 490 this.hideChoices(); 491 } else { 492 this.choices.getChildren(this.options.choicesMatch).each(this.options.injectChoice || 493 function (choice) { 494 var value = choice.innerHTML; 495 choice.inputValue = value; 496 this.addChoiceEvents(choice.set('html', this.markQueryValue(value))); 497 }, this); 498 this.showChoices(); 499 } 500 } 501}); 502Autocompleter.Ajax = { 503 Base: Autocompleter.Request, 504 Json: Autocompleter.Request.JSON, 505 Xhtml: Autocompleter.Request.HTML 506};
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.