Add a Text-Sizing Toolbar to Web Forms





Add a Text-Sizing Toolbar to Web Forms

Insert buttons before <textarea> elements to make the text larger or smaller.

I spend a lot of time—probably too much time—commenting on weblogs and web-based discussion forums. Despite several attempts to create some sort of universal commenting API, virtually all of these sites continue to use a simple web form with a <textarea> element for entering comments.

This hack alters web forms to add a toolbar above every <textarea> element. The toolbar lets you increase or decrease the text size of the <textarea>, without changing the style of the rest of the page. The buttons are fully keyboard-accessible; you can tab to them and press Enter instead of clicking them with your mouse.

I mention this up front, because accessibility matters, and also because it was harder than it sounds.


The Code

This user script runs on all pages. The code looks complicated, and it is complicated, but not for the reason you think. It looks complicated because of the large multiline gibberish-looking strings in the middle of it. Those are data: URIs, which look like hell but are easy to generate.(See "Embed Graphics in a User Script" [Hack #11] for more on data: URIs.)

The toolbar is displayed visually as a row of buttons, but each button is really just an image of something that looks pushable, wrapped in a link that executes one of our JavaScript functions. Since we'll be creating more than one button (this script has only two, but you could easily extend it with more functionality), I created a function to encapsulate all the button-making logic:


	function createButton(target, func, title, width, height, src)


The createButton function takes six arguments:


target

An element object; the <textarea> element that this button will control.


func

A function object; the JavaScript function to be called when the user clicks the button with the mouse or activates it with the keyboard.


title

A string; the text of the tool tip when the user moves her cursor over the button.


width

An integer; the width of the button. This should be the width of the graphic given in the src argument.


height

An integer; the height of the button. This should be the height of the graphic given in the src argument.


src

A string; the URL, path, or data: URI of the button graphic.

Creating the image is straightforward, but creating the link that contains the image is where the real complexity lies:


	button = document.createElement('a');

	button._target = target;

	button.title = title;

	button.href = '#';

	button.onclick = func;

	button.appendChild(img);


There are two things I want to point out here. First, I need to assign a bogus href attribute to the link; otherwise, Firefox would treat it as a named anchor and wouldn't add it to the tab index (i.e., you wouldn't be able to tab to it, making it inaccessible with the keyboard). Second, I'm setting the _target attribute to store a reference to the target <textarea>. This is perfectly legal in JavaScript; you can create new attributes on an object just by assigning them a value. I'll access the custom _target attribute later, in the onclick event handler.

If you read Mozilla's documentation on the Event object, you'll see that there are several target-related properties, including one simply called target. You might be tempted to use event.target to get a reference to the clicked link, but it behaves inconsistently. When the user tabs to the button and presses Enter, event.target is the link, but when the user clicks the button with the mouse, event.target is the image inside the link! In any case, event.currentTarget returns the link in all cases, so I use that.

See http://www.xulplanet.com/references/objref/event.html for documentation on the Event object.


Now the real fun begins. (And you thought you were having fun already!) I need to get the current dimensions and font size of the <textarea> so that I can make them bigger. Simply retrieving the appropriate attributes from textarea.style (textarea.style.width, textarea.style.height, and textarea.style.fontSize) will not work, because those only get set if the page actually defined them in a style attribute on the <textarea> itself. That's not what I want; I want the final style, after all stylesheets have been applied. For that, I need getComputedStyle:


	s = getComputedStyle(textarea, "");

	textarea.style.width = (parseFloat(s.width) * 1.5) + "px";

	textarea.style.height = (parseFloat(s.height) * 1.5) + "px";

	textarea.style.fontSize = (parseFloat(s.fontSize) + 7.0) + 'px';


Finally, do you remember that bogus href value I added to my button link to make sure it was keyboard-accessible? Well, it's now become an annoyance, because after Firefox finishes executing the onclick handler, it's going to try to follow that link. Since it points to a nonexistent anchor, Firefox is going to jump to the top of the page, regardless of where the button is. This is annoying, and to stop it, I need to call event.preventDefault( ) before finishing my onclick handler:


	event.preventDefault();


All this was just for the sake of keyboard accessibility. What can I say? Some people build model airplanes. I build accessible web pages.

Save the following user script as zoomtextarea.user.js:


	// ==UserScript==

	// @name Zoom Textarea

	// @namespace http://diveintomark.org/projects/greasemonkey/

	// @description add controls to zoom textareas

	// @include *

	// ==/UserScript==



	function addEvent(oTarget, sEventName, fCallback, bCapture) {

		var bReturn = false;

		if (oTarget.addEventListener) {

			oTarget.addEventListener(sEventName, fCallback, bCapture);

			bReturn = true;

		} else if (oTarget.attachEvent) {

			bReturn = oTarget.attachEvent('on' + sEventName, fCallback);

		}

		return bReturn;

	}



	function createButton(elmTarget, funcCallback, sTitle, iWidth, iHeight, urlSrc) {

		var elmImage = document.createElement('img');

		elmImage.width = iWidth;

		elmImage.height = iHeight;

		elmImage.style.borderTop = elmImage.style.borderLeft = "1px solid #ccc";

		elmImage.style.borderRight = elmImage.style.borderBottom = "1px solid #888";

		elmImage.style.marginRight = "2px";

		elmImage.src = urlSrc;



		var elmLink = document.createElement('a');

		elmLink.title = sTitle;

		elmLink.href = '#';

		addEvent(elmLink, 'click', funcCallback, true);

		elmLink.appendChild(elmImage);

		return elmLink;

	}



	var arTextareas = document.getElementsByTagName('textarea');

	for (var i = arTextareas.length - 1; i >= 0; i--) {

		var elmTextarea = arTextareas[i];



		function textarea_zoom_in(event) {

			var style = getComputedStyle(elmTextarea, "");

			elmTextarea.style.width = (parseFloat(style.width) * 1.5) + "px";

			elmTextarea.style.height = (parseFloat(style.height) * 1.5) + "px";

			elmTextarea.style.fontSize = (parseFloat(style.fontSize) + 7.0) +

	'px';

			event.preventDefault( );

		}



		function textarea_zoom_out(event) {

			var style = getComputedStyle(elmTextarea, "");

			elmTextarea.style.width = (parseFloat(style.width) * 2.0 / 3.0) +

	"px";

			elmTextarea.style.height = (parseFloat(style.height) * 2.0 / 3.0) +

	"px";

			elmTextarea.style.fontSize = (parseFloat(style.fontSize) - 7.0) +

	"px";

			event.preventDefault( );

		}



		elmTextarea.parentNode.insertBefore(

			createButton(

				elmTextarea,

				textarea_zoom_in,

				'Increase text size',

				20,

				20,

				'data:image/gif;base64,'+

	'R0lGODlhFAAUAOYAANPS1tva3uTj52NjY2JiY7KxtPf3%2BLOys6WkpmJiYvDw8fX19vb'+

	'296Wlpre3uEZFR%2B%2Fv8aqpq9va3a6tr6Kho%2Bjo6bKytZqZml5eYMLBxNra21JSU3'+

	'Jxc3RzdXl4emJhZOvq7KamppGQkr29vba2uGBgYdLR1dLS0lBPUVRTVYB%2Fgvj4%2BYK'+

	'Bg6SjptrZ3cPDxb69wG1tbsXFxsrJy29vccDAwfT09VJRU6uqrFlZW6moqo2Mj4yLjLKy'+

	's%2Fj4%2BK%2Busu7t783Nz3l4e19fX7u6vaalqNPS1MjHylZVV318ftfW2UhHSG9uccv'+

	'KzfHw8qqqrNPS1eXk5tvb3K%2BvsHNydeLi40pKS2JhY2hnalpZWlVVVtDQ0URDRJmZm5'+

	'mYm11dXp2cnm9vcFxcXaOjo0pJSsC%2FwuXk6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'+

	'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC'+

	'H5BAAAAAAALAAAAAAUABQAAAeagGaCg4SFhoeIiYqKTSQUFwgwi4JlB0pOCkEiRQKKRxM'+

	'gKwMGDFEqBYpPRj4GAwwLCkQsijwQBAQJCUNSW1mKSUALNiVVJzIvSIo7GRUaGzUOPTpC'+



	'igUeMyNTIWMHGC2KAl5hCBENYDlcWC7gOB1LDzRdWlZMAZOEJl83VPb3ggAfUnDo5w%2F'+

	'AFRQxJPj7J4aMhYWCoPyASFFRIAA7'),

			elmTextarea);

		elmTextarea.parentNode.insertBefore(

			createButton(

				elmTextarea,

				textarea_zoom_out,

				'Decrease text size',

				20,

				20,

				'data:image/gif;base64,'+

	'R0lGODlhFAAUAOYAANPS1uTj59va3vDw8bKxtGJiYrOys6Wkpvj4%2BPb29%2FX19mJiY'+

	'%2Ff3%2BKqqrLe3uLKytURDRFpZWqmoqllZW9va3aOjo6Kho4KBg729vWJhZK%2BuskZF'+

	'R4B%2FgsLBxHNydY2Mj%2Ff396amptLS0l9fX9fW2dDQ0W1tbpmZm8DAwfT09fHw8n18f'+

	'uLi49LR1V5eYOjo6VBPUa6tr769wEhHSNra20pJStPS1KuqrNPS1ZmYm%2B7t77Kys8rJ'+

	'y%2Fj4%2BaSjpm9uca%2BvsMjHyqalqHRzdVJRU8PDxVRTVcvKzc3Nz0pKS9rZ3evq7MC'+

	'%2FwsXFxp2cnnl4e1VVVu%2Fv8ba2uM7Oz29vcbu6vZqZmnJxc9vb3PHx8uXk5mhnamJh'+

	'Y1xcXZGQklZVV29vcHl4eoyLjKqpq6Wlpl1dXuXk6AAAAAAAAAAAAAAAAAAAAAAAAAAAA'+

	'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'+

	'AAACH5BAAAAAAALAAAAAAUABQAAAeZgGaCg4SFhoeIiYqKR1IWVgcyi4JMBiQqA0heQgG'+

	'KQTFLPQgMCVocBIoNNqMgCQoDVReKYlELCwUFI1glEYorOgopWSwiTUVfih8dLzRTKA47'+

	'Ek%2BKBGE8GEAhFQYuPooBOWAHY2ROExBbSt83QzMbVCdQST8Ck4QtZUQe9faCABlGrvD'+

	'rB4ALDBMU%2BvnrUuOBQkE4NDycqCgQADs%3D'),

			elmTextarea);

		elmTextarea.parentNode.insertBefore(

			document.createElement('br'),

			elmTextarea);

	}


Running the Hack

After installing the user script (Tools Install This User Script), go to http://philringnalda.com/blog/2005/06/ms_embraces_rss.php. At the bottom of the page is a form for submitting comments. Above the form, you will see two buttons inserted by the user script, as shown in Figure.

Type some text into the <textarea>, then click the first button (titled "Increase text size") to make the text and the <textarea> larger, as shown in Figure. Alternatively, while focus is in the <textarea>, you can press Shift-Tab twice to set focus to the Zoom In button, and then press Enter to activate the button.

Click the second button, titled "Decrease text size," to make the text smaller. Due to rounding, if you repeatedly zoom in and then repeatedly zoom out, the text and its surrounding box may end up a slightly different size. The zooming is not permanent, so you can refresh the page to return to the original size.

Zoom toolbar in web form


Zoomed web form



     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows