LivePipe has been deprecated and is no longer maintained.
The author now works primarily on Thorax

Control.TextArea

Cross browser textarea manipulation for Prototype.

Introduction

WYSIWYG editing in the browser is still difficult to integrate or extend, and generated markup is often suspect. In the meantime a crop of text formatting languages such as Markdown, Textile, BBCode and wiki markup have emerged as ways for users to enter rich content in a controlled enviornment.

Control.TextArea provides a text selection and manipulation API, as well as a simple toolbar API, but leaves the application integration and feature set to the developer.

Example for Markdown

Preview

HTML


		<textarea id="markdown_example"></textarea>
		<div id="markdown_formatted"></div>
	

JavaScript


	//setup
	var textarea = new Control.TextArea('markdown_example');
	var toolbar = new Control.TextArea.ToolBar(textarea);
	toolbar.container.id = 'markdown_toolbar'; //for css styles
	
	//preview of markdown text
	var converter = new Showdown.converter;
	var converter_callback = function(value){
		$('markdown_formatted').update(converter.makeHtml(value));
	}
	converter_callback(textarea.getValue());
	textarea.observe('change',converter_callback);
	
	//buttons
	toolbar.addButton('Italics',function(){
		this.wrapSelection('*','*');
	},{
		id: 'markdown_italics_button'
	});
	
	toolbar.addButton('Bold',function(){
		this.wrapSelection('**','**');
	},{
		id: 'markdown_bold_button'
	});
	
	toolbar.addButton('Link',function(){
		var selection = this.getSelection();
		var response = prompt('Enter Link URL','');
		if(response == null)
			return;
		this.replaceSelection('[' + (selection == '' ? 'Link Text' : selection) + '](' + (response == '' ? 'http://link_url/' : response).replace(/^(?!(f|ht)tps?:\/\/)/,'http://') + ')');
	},{
		id: 'markdown_link_button'
	});
	
	toolbar.addButton('Image',function(){
		var selection = this.getSelection();
		var response = prompt('Enter Image URL','');
		if(response == null)
			return;
		this.replaceSelection('![' + (selection == '' ? 'Image Alt Text' : selection) + '](' + (response == '' ? 'http://image_url/' : response).replace(/^(?!(f|ht)tps?:\/\/)/,'http://') + ')');
	},{
		id: 'markdown_image_button'
	});
	
	toolbar.addButton('Heading',function(){
		var selection = this.getSelection();
		if(selection == '')
			selection = 'Heading';
		this.replaceSelection("\n" + selection + "\n" + $R(0,Math.max(5,selection.length)).collect(function(){'-'}).join('') + "\n");
	},{
		id: 'markdown_heading_button'
	});
	
	toolbar.addButton('Unordered List',function(event){
		this.collectFromEachSelectedLine(function(line){
			return event.shiftKey ? (line.match(/^\*{2,}/) ? line.replace(/^\*/,'') : line.replace(/^\*\s/,'')) : (line.match(/\*+\s/) ? '*' : '* ') + line;
		});
	},{
		id: 'markdown_unordered_list_button'
	});
	
	toolbar.addButton('Ordered List',function(event){
		var i = 0;
		this.collectFromEachSelectedLine(function(line){
			if(!line.match(/^\s+$/)){
				++i;
				return event.shiftKey ? line.replace(/^\d+\.\s/,'') : (line.match(/\d+\.\s/) ? '' : i + '. ') + line;
			}
		});
	},{
		id: 'markdown_ordered_list_button'
	});
	
	toolbar.addButton('Block Quote',function(event){
		this.collectFromEachSelectedLine(function(line){
			return event.shiftKey ? line.replace(/^\> /,'') : '> ' + line;
		});
	},{
		id: 'markdown_quote_button'
	});
	
	toolbar.addButton('Code Block',function(event){
		this.collectFromEachSelectedLine(function(line){
			return event.shiftKey ? line.replace(/    /,'') : '    ' + line;
		});
	},{
		id: 'markdown_code_button'
	});
	
	toolbar.addButton('Help',function(){
		window.open('http://daringfireball.net/projects/markdown/dingus');
	},{
		id: 'markdown_help_button'
	});

CSS


	#markdown_example {
		width:100%;
		height:200px;
	}

	#markdown_toolbar {
		position:relative;
		list-style:none;
		border:1px solid #d7d7d7;
		background-color:#F6F6F6;
		margin:0;
		padding:0;
		height:18px;
		margin-bottom:2px;
	}

	#markdown_toolbar li {
		list-style:none;
		margin:0;
		padding:0;
		float:left;
	}

	#markdown_toolbar li a {
		width:24px;
		height:16px;
		float:left;
		display:block;
		background-image:url("/stylesheets/markdown_icons.gif");
		border:1px solid #fff;
		border-right-color:#d7d7d7;
	}

	#markdown_toolbar li a:hover {
		border-color:#900;
	}

	#markdown_toolbar li span {
		display:none;
	}

	#markdown_toolbar li a#markdown_help_button {
		position:absolute;
		top:0;
		right:0;
		border-left-color:#d7d7d7;
		border-right-color:#fff;
	}

	#markdown_toolbar li a#markdown_help_button:hover {
		border-left-color:#900;
		border-right-color:#900;
	}

	#markdown_italics_button { background-position: 0 -119px; }
	#markdown_bold_button { background-position: 0 -102px; }
	#markdown_link_button { background-position: 0 0; }
	#markdown_image_button { background-position: 0 -170px; }
	#markdown_unordered_list_button { background-position: 0 -34px; }
	#markdown_ordered_list_button { background-position: 0 -51px; }
	#markdown_quote_button { background-position: 0 -68px; }
	#markdown_code_button { background-position: 0 -136px; }
	#markdown_help_button { background-position: 0 -153px; }
	#markdown_heading_button { background-position: 0 -85px; }

DOM Modifications

Control.TextArea.Toolbar.initialize() will insert a div which contains the toolbar before the textarea which belongs to the Control.TextArea instance you pass in.

Class

ReturnNameDescription
numberonChangeTimeoutLengthLength of time before the change event is triggered.

Instance

ReturnNameDescription
Control.TextAreainitialize(Element textarea)
nullcollectFromEachSelectedLine(function callback [,string before_text [,string after_text]])Works like Enumerable.collect(). The callback will be called with each selected line, and the return array will be joined, and will replace the current selection.
stringgetSelection()Currently selected text.
stringgetValue()Contents of the textarea.
nullinsertAfterSelection(string text)
nullinsertBeforeEachSelectedLine(string text [,string before_text [,string after_text]])
nullinsertBeforeSelection(string text)
nullreplaceSelection(string new_text)Replace the currently selected text.
nullwrapSelection(string before_text, string after_text)Wrap the currently selected text.
ElementelementTextarea element.

Control.TextArea.Toolbar Instance

ReturnNameDescription
Control.TextArea.ToolBarinitialize(Control.TextArea instance [,mixed toolbar_id])If no toolbar_id (Element or string id) is passed, a UL element is inserted before the textarea element in the Control.TextArea instance.
nulladdButton(string link_text, function callback [,Hash attributes])Creates an LI element with an A element inside and attaches it to the toolbar, callback will be bound to the Control.TextArea instance. If attributes are present they will be attached to the A element.
nullattachButton(Element node, function callback)Attach a callback to a node that already exists on the page, callback will be bound to the Control.TextArea instance.
ElementcontainerThe toolbar Element.
Control.TextAreatextareaThe Control.TextArea instance the toolbar is attached to.

Events

NameDescription
change(string new_value)This event exists because the 'change' event on the textarea element is not fired when we modify the contents via this script. Observing 'change' on the Control.TextArea instance instead will allow you to observe when the textarea changes from key presses, manipulation from Control.TextArea, and on cuts and pastes for browsers that allow it.