Pastie now auto-senses if line-wrap is a bad or good idea. Feedback?
## mark a section (Learn more)
Element.implement({ getCaretPosition: function() { if (this.createTextRange) { var r = document.selection.createRange().duplicate(); r.moveEnd('character', this.value.length); if (r.text === '') return this.value.length; return this.value.lastIndexOf(r.text); } else return this.selectionStart; } }); var ResizableTextbox = new Class({ Implements: Options, options: { min: 5, max: 500, step: 7 }, initialize: function(element, options) { var that = this; this.setOptions(options); this.el = $(element); this.width = this.el.offsetWidth; this.el.addEvents({ 'keydown': function() { this.store('rt-value', this.get('value').length); }, 'keyup': function() { var newsize = that.options.step * this.get('value').length; if(newsize <= that.options.min) newsize = that.width; if(! (this.get('value').length == this.retrieve('rt-value') || newsize <= that.options.min || newsize >= that.options.max)) this.setStyle('width', newsize); } }); } }); var TextboxList = new Class({ Implements: [Events, Options], options: {/* onFocus: $empty, onBlur: $empty, onInputFocus: $empty, onInputBlur: $empty, onBoxFocus: $empty, onBoxBlur: $empty, onBoxDispose: $empty,*/ resizable: {}, className: 'bit', separator: '###', extrainputs: true, startinput: true, hideempty: true }, initialize: function(element, options) { this.setOptions(options); this.element = $(element).setStyle('display', 'none'); this.bits = new Hash; this.events = new Hash; this.count = 0; this.current = false; this.maininput = this.createInput({'class': 'maininput'}); this.holder = new Element('ul', { 'class': 'holder', 'events': { 'click': function(e) { e = new Event(e).stop(); if(this.maininput != this.current) this.focus(this.maininput); }.bind(this) } }).inject(this.element, 'before').adopt(this.maininput); this.makeResizable(this.maininput); this.setEvents(); }, setEvents: function() { document.addEvent(Browser.Engine.trident ? 'keydown' : 'keypress', function(e) { if(! this.current) return; if(this.current.retrieve('type') == 'box' && e.code == Event.Keys.backspace) new Event(e).stop(); }.bind(this)); document.addEvents({ 'keyup': function(e) { e = new Event(e).stop(); if(! this.current) return; switch(e.code){ case Event.Keys.left: return this.move('left'); case Event.Keys.right: return this.move('right'); case Event.Keys.backspace: return this.moveDispose(); } }.bind(this), 'click': function() { this.fireEvent('onBlur').blur(); }.bind(this) }); }, update: function() { this.element.set('value', this.bits.getValues().join(this.options.separator)); return this; }, add: function(text, html) { var id = this.options.className + '-' + this.count++; var el = this.createBox($pick(html, text), {'id': id}).inject(this.current || this.maininput, 'before'); el.addEvent('click', function(e) { e = new Event(e).stop(); this.focus(el); }.bind(this)); this.bits.set(id, text); if(this.options.extrainputs && (this.options.startinput || el.getPrevious())) this.addSmallInput(el, 'before'); return el; }, addSmallInput: function(el, where) { var input = this.createInput({'class': 'smallinput'}).inject(el, where); input.store('small', true); this.makeResizable(input); if(this.options.hideempty) input.setStyle('display', 'none'); return input; }, dispose: function(el) { this.bits.remove(el.id); if(el.getPrevious().retrieve('small')) el.getPrevious().destroy(); if(this.current == el) this.focus(el.getNext()); if(el.retrieve('type') == 'box') this.fireEvent('onBoxDispose', el); el.destroy(); return this; }, focus: function(el, nofocus) { if(! this.current) this.fireEvent('onFocus', el); else if(this.current == el) return this; this.blur(); el.addClass(this.options.className + '-' + el.retrieve('type') + '-focus'); if(el.retrieve('small')) el.setStyle('display', 'block'); if(el.retrieve('type') == 'input') { this.fireEvent('onInputFocus', el); if(! nofocus) this.callEvent(el.retrieve('input'), 'focus'); } else this.fireEvent('onBoxFocus', el); this.current = el; return this; }, blur: function(noblur) { if(! this.current) return this; if(this.current.retrieve('type') == 'input') { var input = this.current.retrieve('input'); if(! noblur) this.callEvent(input, 'blur'); this.fireEvent('onInputBlur', input); } else this.fireEvent('onBoxBlur', this.current); if(this.current.retrieve('small') && ! input.get('value') && this.options.hideempty) this.current.setStyle('display', 'none'); this.current.removeClass(this.options.className + '-' + this.current.retrieve('type') + '-focus'); this.current = false; return this; }, createBox: function(text, options) { return new Element('li', $extend(options, {'class': this.options.className + '-box'})).set('html', text).store('type', 'box'); }, createInput: function(options) { var li = new Element('li', {'class': this.options.className + '-input'}); var el = new Element('input', $extend(options, { 'type': 'text', 'events': { 'click': function(e) { e = new Event(e).stop(); }, 'focus': function(e) { if(! this.isSelfEvent('focus')) this.focus(li, true); }.bind(this), 'blur': function() { if(! this.isSelfEvent('blur')) this.blur(true); }.bind(this), 'keydown': function(e) { this.store('lastvalue', this.value).store('lastcaret', this.getCaretPosition()); } } })); return li.store('type', 'input').store('input', el).adopt(el); }, callEvent: function(el, type) { this.events.set(type, el); el[type](); }, isSelfEvent: function(type) { return (this.events.get(type)) ? !! this.events.remove(type) : false; }, makeResizable: function(li) { var el = li.retrieve('input'); el.store('resizable', new ResizableTextbox(el, $extend(this.options.resizable, {min: el.offsetWidth, max: this.element.getStyle('width').toInt()}))); return this; }, checkInput: function() { var input = this.current.retrieve('input'); return (! input.retrieve('lastvalue') || (input.getCaretPosition() === 0 && input.retrieve('lastcaret') === 0)); }, move: function(direction) { var el = this.current['get' + (direction == 'left' ? 'Previous' : 'Next')](); if(el && (! this.current.retrieve('input') || ((this.checkInput() || direction == 'right')))) this.focus(el); return this; }, moveDispose: function() { if(this.current.retrieve('type') == 'box') return this.dispose(this.current); if(this.checkInput() && this.bits.getKeys().length && this.current.getPrevious()) return this.focus(this.current.getPrevious()); } }); var FacebookList = new Class({ Extends: TextboxList, options: { onBoxDispose: function(item) { this.autoFeed(item.retrieve('text')); }, onInputFocus: function() { this.autoShow(); }, onInputBlur: function(el) { this.lastinput = el; this.blurhide = this.autoHide.delay(200, this); }, autocomplete: { 'opacity': 0.8, 'maxresults': 10, 'minchars': 1 } }, initialize: function(element, autoholder, options) { arguments.callee.parent(element, options); this.data = []; this.autoholder = $(autoholder).set('opacity', this.options.autocomplete.opacity); this.autoresults = this.autoholder.getElement('ul'); var children = this.autoresults.getElements('li'); children.each(function(el) { this.add(el.innerHTML); }, this); }, autoShow: function(search) { this.autoholder.setStyle('display', 'block'); this.autoholder.getChildren().setStyle('display', 'none'); if(! search || ! search.trim() || (! search.length || search.length < this.options.autocomplete.minchars)) { this.autoholder.getElement('.default').setStyle('display', 'block'); this.resultsshown = false; } else { this.resultsshown = true; this.autoresults.setStyle('display', 'block').empty(); this.data.filter(function(str) { return str ? str.test(search, 'i') : false; }).each(function(result, ti) { if(ti >= this.options.autocomplete.maxresults) return; var that = this; var el = new Element('li').addEvents({ 'mouseenter': function() { that.autoFocus(this); }, 'click': function(e) { new Event(e).stop(); that.autoAdd(this); } }).set('html', this.autoHighlight(result, search)).inject(this.autoresults); el.store('result', result); if(ti == 0) this.autoFocus(el); }, this); } return this; }, autoHighlight: function(html, highlight) { return html.replace(new RegExp(highlight, 'gi'), function(match) { return '<em>' + match + '</em>'; }); }, autoHide: function() { this.resultsshown = false; this.autoholder.setStyle('display', 'none'); return this; }, autoFocus: function(el) { if(! el) return; if(this.autocurrent) this.autocurrent.removeClass('auto-focus'); this.autocurrent = el.addClass('auto-focus'); return this; }, autoMove: function(direction) { if(!this.resultsshown) return; this.autoFocus(this.autocurrent['get' + (direction == 'up' ? 'Previous' : 'Next')]()); return this; }, autoFeed: function(text) { this.data.include(text); return this; }, autoAdd: function(el) { if(!el || ! el.retrieve('result')) return; this.add(el.retrieve('result')); delete this.data[this.data.indexOf(el.retrieve('result'))]; this.autoHide(); var input = this.lastinput || this.current.retrieve('input'); input.set('value', '').focus(); return this; }, createInput: function(options) { var li = arguments.callee.parent(options); var input = li.retrieve('input'); input.addEvents({ 'keydown': function(e) { this.dosearch = false; switch(new Event(e).code) { case Event.Keys.up: return this.autoMove('up'); case Event.Keys.down: return this.autoMove('down'); case Event.Keys.enter: if(! this.autocurrent) break; this.autoAdd(this.autocurrent); this.autocurrent = false; this.autoenter = true; break; case Event.Keys.esc: this.autoHide(); if(this.current && this.current.retrieve('input')) this.current.retrieve('input').set('value', ''); break; default: this.dosearch = true; } }.bind(this), 'keyup': function() { if(this.dosearch) this.autoShow(input.value); }.bind(this) }); input.addEvent(Browser.Engine.trident ? 'keydown' : 'keypress', function(e) { if(this.autoenter) new Event(e).stop() this.autoenter = false; }.bind(this)); return li; }, createBox: function(text, options) { var li = arguments.callee.parent(text, options); return li.addEvents({ 'mouseenter': function() { this.addClass('bit-hover') }, 'mouseleave': function() { this.removeClass('bit-hover') } }).adopt(new Element('a', { 'href': '#', 'class': 'closebutton', 'events': { 'click': function(e) { new Event(e).stop(); if(! this.current) this.focus(this.maininput); this.dispose(li); }.bind(this) } })).store('text', text); } });
This paste will be private.
From the Design Piracy series on my blog: