Greasemonkey Hacks/Beautifying the Web
From WikiContent
(Initial conversion from Docbook) |
(Initial conversion from Docbook) |
Current revision
Contents |
Hacks 21–28: Introduction
Graphic designers have long decried the constraints of the Web. Differences across browsers and platforms destroy their carefully designed layouts. Everything takes too long to load, destroying their hopes for the user experience. And don't even get them started about fonts.
But all is not lost. The Web might look grungy and minimalist compared to a well-laid-out magazine or book, but that doesn't mean we can't take a few steps in the right direction. The hacks in this chapter focus on making the Web a kinder, gentler, more visually appealing place to visit. Well, more visually appealing anyway. Kindness and gentleness: you're on your own for those.
Banish the Scourge of Arial
Make the Web a typographically better place.
The Arial font is the bane of typographical snobs everywhere. Originally conceived as a cheap clone of Helvetica (due to licensing fees), Arial was adopted by Microsoft in Windows 3.1 and has since taken over the world. Firefox uses Arial as one of the default fonts for web pages that don't specify a default. Despite the rich capabilities for specifying multiple fallback fonts in modern browsers, Arial continues to dominate typography on the Web.
The first thing I do when I reinstall Windows (and the first thing you should do before running this hack) is change the default font in Firefox. Under Windows, select Tools → Options to open the preferences dialog. In the General pane, click the Fonts & Colors button and change the Sans-Serif font from Arial to something else. I'm partial to Helvetica or Verdana on Windows, and Mac OS X comes with a handsome font called Optima. But almost any choice is better than Arial.
Tip
Read more about the history of Arial at http://www.msstudio.com/articles.html.
The Code
This user script runs on all pages. It iterates through all the elements on the page and gets the element's style (using getComputedStyle), then removes Arial from the list of fonts for that element.
Tip
You might think that you could simply access an element's style by checking its style attribute. But this attribute includes only inline styles defined in a style attribute on the original page. It doesn't include styles applied by external stylesheets. To get an element's actual style, you need to call the getComputedStyle function.
Save the following user script as scourgeofarial.user.js:
// ==UserScript==
// @name Scourge of Arial
// @namespace http://diveintomark.org/projects/greasemonkey/
// @description banish the scourge of Arial
// @include *
// ==/UserScript==
var arElements = document.getElementsByTagName('*');
for (var i = arElements.length - 1; i >= 0; i--) {
var elm = arElements[i];
var style = getComputedStyle(elm, '');
elm.style.fontFamily = style.fontFamily.replace(/arial/i, '');
}
Running the Hack
As I mentioned before, the first thing you should do is change your default sans-serif font from Arial to something else. If you don't do this, this hack won't have any effect, because an element with a font declaration of Arial, sans-serif will be changed to sans-serif and use the default font, but the default font would still be Arial!
Before installing the user script, go to http://www.google.com, which uses Arial for all the text and links on the page, as shown in Figure 3-1.
Now, install the user script (Tools → Install This User Script), and refresh http://www.google.com. You will see the text and links change to the font you defined as your default sans-serif font in the Firefox preferences dialog. I changed mine to Verdana, as shown in Figure 3-2.
Hacking the Hack
Currently, this hack only removes Arial; it doesn't replace it with anything. Web pages that define Arial as the only font will end up with no font declaration at all, and Firefox will display them with a serif font such as Times. To get around this problem, we can update the script to substitute sansserif instead of removing Arial altogether.
Change this line:
elm.style.font defaultsfontFamily = style.fontFamily.replace(/arial/i, '');
to this:
elm.style.fontFamily = style.fontFamily.replace(/arial/i, 'sans-serif');
Add Stripes to Data Tables
Make tables easier to read by highlighting alternate rows.
Web pages can display tables of data, like a spreadsheet. However, most web publishers don't put a lot of thought into the usability of large tables. Small improvements such as highlighting every other row can make a huge difference in readability. I honestly didn't think such a little detail would matter to me, since I have normal eyesight and don't spend a lot of time poring over reports or spreadsheets online. But the difference is amazing! I can't imagine how I ever lived without this hack.
The Code
This user script runs on all pages. It is relatively straightforward. It gets all the table rows (<tr> elements) and then loops through them to set the background color to #ddd or #fff.
Save the following user script as tablestripes.user.js:
// ==UserScript==
// @name Table Stripes
// @namespace http://diveintomark.org/projects/greasemonkey/
// @description shade alternating rows of data tables
// @include *
// ==/UserScript==
var arTableRows = document.getElementsByTagName('tr');
var bHighlight = true;
for (var i = arTableRows.length - 1; i >= 0; i--) {
var elmRow = arTableRows[i];
elmRow.style.backgroundColor = bHighlight ? '#ddd' : '#fff';
elmRow.style.color = '#000';
bHighlight = !bHighlight;
}
Running the Hack
Before installing the user script, go to http://www.openbsd.org/3.7_packages/i386.html, which displays a large table of available packages for the Open-BSD operating system, as shown in Figure 3-3.
Now install the user script (Tools → Install This User Script), and refresh http://www.openbsd.org/3.7_packages/i386.html. You will see every other row is now slightly shaded, as shown in Figure 3-4. This makes it much easier to read across the table and associate the package name with its description.
Hacking the Hack
Currently, this hack shades alternating rows in any table. But many web pages use tables for layout, and this hack could seriously alter their display in unexpected and bizarre ways. To get around this, we can use XPath to operate only on tables that include table headers (<th> elements). Table headers are rarely used in layout tables; usually, you find them only in tables that actually display tabular data.
Save the following user script as tablestripes2.user.js:
// ==UserScript==
// @name Table data tablesadding stripesStripes
// @namespace http://diveintomark.org/projects/greasemonkey/
// @description shade alternating rows of data tables
// @include *
// ==/UserScript==
var snapTableRows = document.evaluate("//table//th/ancestor::table//tr",
document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
var bHighlight = true;
for (var i = snapTableRows.snapshotLength - 1; i >= 0; i--) {
var elmRow = snapTableRows.snapshotItem(i);
elmRow.style.backgroundColor = bHighlight ? '#ddd' : '#fff';
elmRow.style.color = '#000';
bHighlight = !bHighlight;
}
There is another obvious candidate for striping: lists. Simply by taking the original hack and changing the first line of code to search for <li> elements instead of <tr> elements, we can highlight alternating items in ordered and unordered lists.
Save the following user script as liststripes.user.js:
// ==UserScript==
// @name List Stripes
// @namespace http://diveintomark.org/projects/greasemonkey/
// @description shade alternating rows of lists
// @include *
// ==/UserScript==
var arListItems = document.getElementsByTagName('li');
var bHighlight = true;
for (var i = arListItems.length - 1; i >= 0; i--) {
var elmListItem = arListItems[i];
elmListItem.style.backgroundColor = bHighlight ? '#ddd' : '#fff';
elmListItem.style.color = '#000';
bHighlight = !bHighlight;
}
Yahoo! uses lists to display search results, so you can see this effect by searching for something in Yahoo! Web Search, as shown in Figure 3-5.
Every other search result is slightly shaded, which makes it easier to scan the page when you need to look past the first search result.
Straighten Smart Quotes
Convert curly quotes, apostrophes, and other fancy typographical symbols back to their ASCII equivalents.
Have you ever gone to copy a block of text from a web site and paste it into a text editor (or try to paste it into a weblog post of your own)? The text comes through, but all the apostrophes and quote marks end up as random-looking symbols. The web site uses fancy publishing software to produce smart quotes and apostrophes, but your text editor doesn't understand them. This hack dumbs down these fancy typographical symbols to their ASCII equivalents.
The Code
This user script runs on all pages. It constructs an array of fancy characters (by their Unicode representation). Then, it gets a list of all the text nodes on the page and executes a search-and-replace on each node to convert each fancy character to a plain-text equivalent.
Tip
Learn more about Unicode at http://www.unicode.org.
In JavaScript, the replace method takes a regular expression object as its first parameter. For performance reasons, we build all our regular expressions first, and then reuse them every time through the loop. If we had used the inline regular expression syntax, Firefox would need to rebuild each regular expression object every time through the loop—a significant performance drain on large pages!
Save the following user script as dumbquotes.user.js:
// ==UserScript==
// @name smart quotesDumbQuotes
// @namespace http://diveintomark.org/projects/greasemonkey/
// @description straighten curly quotes and apostrophes
// @include *
// ==/UserScript==
var arReplacements = {
"\xa0": " ",
"\xa9": "(c)",
"\xae": "(r)",
"\xb7": "*",
"\u2018": "'",
"\u2019": "'",
"\u201c": '"',
"\u201d": '"',
"\u2026": "…",
"\u2002": " ",
"\u2003": " ",
"\u2009": " ",
"\u2013": "-",
"\u2014": "--",
"\u2122": "(tm)"};
var arRegex = new Array();
for (var sKey in arReplacements) {
arRegex[sKey] = new RegExp(sKey, 'g');
}
var snapTextNodes = document.evaluate("//text()[" +
"not(ancestor::script) and not(ancestor::style)]",
document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i = snapTextNodes.snapshotLength - 1; i >= 0; i--) {
var elmTextNode = snapTextNodes.snapshotItem(i);
var sText = elmTextNode.data;
for (var sKey in arReplacements) {
sText = sText.replace(arRegex[sKey], arReplacements[sKey]);
}
elmTextNode.data = sText;
}
Running the Hack
Before installing the user script, go to http://www.alistapart.com/articles/emen/. As shown in Figure 3-6, the fourth paragraph reads "But the larger problem is, now that they're available, almost no one publishing on the web today knows how to use them—or often even knows of their existence." There are two fancy characters here: the apostrophe in the word they're and the dash between them and or.
Now, install the user script (Tools → Install This User Script) and refresh the page at http://www.alistapart.com/articles/emen/. As shown in Figure 3-7, the two fancy characters have been replaced with their ASCII equivalents. The apostrophe has been converted to a straight apostrophe, and the dash has been replaced with two hyphen characters.
Although this hack currently focuses on typographical symbols, there is nothing typography-specific about it. It's just a generic script that does global search-and-replace on the text of a web page. By altering the arReplacements array, you can replace any character, word, or phrase with anything else, on any web page. Obviously, this can lead to all sorts of mischief, if you were so inclined. I will leave this one up to your imagination….
Convert Graphical Smileys to Text
Are you tired of little smiley icons infesting web pages and discussion forums? Convert them back to text!
I originally wrote this hack in response to a joke. Someone on the Greasemonkey mailing list announced that he had developed a user script to convert ASCII smileys such as :-) to their graphical equivalents. Someone else responded, wondering how long it would take for someone to do the reverse: convert graphical smileys back to text.
For the record, it took me about 20 minutes. Most of the time was spent researching publishing software that autogenerated graphical smileys and compiling a comprehensive list of variations.
Tip
The list of smileys in this hack was taken from http://www.phpbb.com/admin_demo/admin_smilies.htm. PHPBB is a popular application for hosting web-based discussion forums.
The Code
This user script runs on all pages. It relies on the fact that most graphical smileys are autogenerated by web publishing software, and the software puts the text equivalent of the smiley in the image's alt attribute. This means we can find images that are smileys by checking the alt attribute against a list of known values. Images that are not smileys, but just happen to have useful alternate text, will not be affected.
Save the following user script as frownies.user.js:
// ==UserScript==
// @name Frownies
// @namespace http://diveintomark.org/projects/greasemonkey/
// @description convert graphical smilies to text
// @include *
// ==/UserScript==
var arSmilies = [
":)", ":-)", ":-(", ":(", ";-)", ";)", ":-D", ":D", ":-/",
":/", ":X", ":-X", ":\">", ":P", ":-P", ":O", ":-O", "X-(",
"X(", ":->", ":>", "B-)", "B)", ">:)", ":((", ":(((", ":-((",
":))", ":-))", ":-|", ":|", "O:-)", "O:)", ":-B", ":B", "=;",
"I)", "I-)", "|-)", "|)", ":-&", ":&", ":-$", ":$", "[-(", ":O)",
":@)", "3:-O", ":(|)", "@};-", "**==", "(~~)", "*-:)", "8-X",
"8X", "=:)", "<):)", ";;)", ":*", ":-*", ":S", ":-S", "/:)",
"/:-)", "8-|", "8|", "8-}", "8}", "(:|", "=P~", ":-?", ":?",
"#-O", "#O", "=D>", "~:>", "%%-", "~O)", ":-L", ":L", "[-O<",
"[O<", "@-)", "@)", "$-)", "$)", ">-)", ":-\"", ":^O", "B-(",
"B(", ":)>-", "[-X", "[X", "\\:D/", ">:D<", "(%)", "=((", "#:-S",
"#:S", "=))", "L-)", "L)", "<:-P", "<:P", ":-SS", ":SS", ":-W",
":W", ":-<", ":<", ">:P", ">:-P", ">:/", ";))", ":-@", "^:)^",
":-J", "(*)", ":GRIN:", ":SMILE:", ":SAD:", ":EEK:",
":SHOCK:", ":???:", "8)", "8-)", ":COOL:", ":LOL:", ":MAD:",
":RAZZ:", ":OOPS:", ":CRY:", ":EVIL:", ":TWISTED:", ":ROLL:",
":WINK:", ":!:", ":?:", ":IDEA:", ":ARROW:", ":NEUTRAL:",
":MRGREEN:"];
var snapImages = document.evaluate("//img[@alt]", document, null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i = snapImages.snapshotLength - 1; i >= 0; i--) {
var elmImage = snapImages.snapshotItem(i);
var sAltText = elmImage.alt.toUpperCase();
for (var j = arSmilies.length - 1; j >= 0; j--) {
if (sAltText == arSmilies[j]) {
var elmReplacementText = document.createTextNode(sAltText);
elmImage.parentNode.replaceChild(elmReplacementText, elmImage);
}
}
}
Running the Hack
Before installing the user script (Tools → Install This User Script), go to http://www.phpbb.com/admin_demo/admin_smilies.htm. This page demonstrates the smiley capabilities of the PHPBB application. Users type text into their forum post, and PHPBB converts the text to a graphical smiley based on a set of rules, as shown in Figure 3-8.
Now, install the user script from Tools/Install This User Script, and then refresh the page at http://www.phpbb.com/admin_demo/admin_smilies.htm. All the graphical smileys will be replaced by their original text equivalents, as shown in Figure 3-9.
Hacking the Hack
A big part of this hack is devoted to finding all the graphical smileys on a page by looking for images with a certain alt attribute. Once you find them, you can do anything you want with them. For example, if you want to remove the smileys altogether, replace the inner for loop with this:
for (var j = arSmilies.length - 1; j >= 0; j--) {
if (sAltText == arSmilies[j]) {
elmImage.parentNode.removeChild(elmImage);
}
Make Amazon Product Images Larger
Amazon lets you see larger product images in a separate window. Display them inline instead.
Amazon product pages contain a wealth of information, including a medium-sized image of the product. Clicking on the product image opens a new window to display a larger version. This is fine for most screens, but if you're lucky enough to be using a modern laptop or a high-resolution monitor, you have plenty of real estate on your screen to display the larger product image inline on the product page itself.
The Code
This user script will run on all Amazon pages. The code itself is divided into three parts:
- Find the product image
- If you're looking at a non-product page, or a product for which Amazon doesn't have an image, the script will exit without modifying anything.
- Reset hardcoded widths
- Amazon wraps the product image inside a <div> that hardcodes the image width. We need to reset that width so the larger image will display properly.
- Replace the product image
- This is a simple matter of creating a new <img> element that points to the larger version of the product image, and then replacing the existing <img> element.
Save the following user script as amazonlarger.user.js:
// ==UserScript==
// @name web sitesappearanceAmazon products images, enlargingAmazon Larger Amazon.comproducts imagesImages
// @namespace http://diveintomark.org/projects/greasemonkey/
// @description display larger product images on Amazon
// @include http://amazon.tld/*
// @include http://*.amazon.tld/*
// ==/UserScript==
var elmProductImage = document.evaluate(
"//img[contains(@src, 'MZZZZZZZ')]", document, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (!elmProductImage) return;
var elmParent = elmProductImage.parentNode;
while (elmParent && (elmParent.nodeName != 'BODY')) {
elmParent.style.width = 'auto';
elmParent.style.height = 'auto';
elmParent = elmParent.parentNode;
}
var elmNewImage = document.createElement('img');
elmNewImage.src = elmProductImage.src.replace(/MZZZZZZZ/, 'LZZZZZZZ');
elmNewImage.style.border = '0';
elmProductImage.parentNode.replaceChild(elmNewImage, elmProductImage);
Running the Hack
After installing the user script (Tools → Install This User Script), go to http://www.amazon.com and search for anything—for example, Dave Matthews Band Stand Up. When you click through to the product page, you will see the larger version of the album cover, as shown in Figure 3-10.
Hacking the Hack
If you don't want to see the product images at all, you can simplify the script immensely:
var elmProductImage = document.evaluate(
"//img[contains(@src, 'MZZZZZZZ')]", document, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (!elmProductImage) return;
elmProductImage.parentNode.removeChild(elmProductImage);
Convert Straight Quotes
Automatically convert straight quotes to "smart" quotes, like word processors do.
You are typographically cool. You use smart publishing software to automatically convert boring straight quotes and apostrophes to their curly Unicode equivalents. Don't you also want to flaunt your coolness when you post comments on other sites? Now you can! This hack takes straight quotes and other plain typography in web forms and replaces them with their curly equivalents.
The Code
This user script runs on all pages. It traps form submission and smartens straight quotes in <textarea> elements of web forms.
Save the following user script as smartquotes.user.js:
// ==UserScript==
// @name Smart Quotes
// @namespace http://www.slightlyremarkable.com/
// @description Curlify typography in web forms
// @include *
// ==/UserScript==
// based on code by Jonathan Fenocchi
// and included here with his gracious permission
function filterChars(formatted) {
var temp = new Array();
var count = 0;
var DELIM_CHAR = '\u00A4';
var BASE_CHAR = '\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-'+
'\u00F6\u00F8-\u00FF\u0100-\u0131\u0134-\u013E\u0141-\u0148\u014A-'+
'\u017E\u0180-\u01C3\u01CD-\u01F0\u01F4-\u01F5\u01FA-\u0217\u0250-'+
'\u02A8\u02BB-\u02C1\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-'+
'\u03CE\u03D0-\u03D6\u03DA\u03DC\u03DE\u03E0\u03E2-\u03F3\u0401-'+
'\u040C\u040E-\u044F\u0451-\u045C\u045E-\u0481\u0490-\u04C4\u04C7-'+
'\u04C8\u04CB-\u04CC\u04D0-\u04EB\u04EE-\u04F5\u04F8-\u04F9\u0531-'+
'\u0556\u0559\u0561-\u0586\u05D0-\u05EA\u05F0-\u05F2\u0621-\u063A'+
'\u0641-\u064A\u0671-\u06B7\u06BA-\u06BE\u06C0-\u06CE\u06D0-\u06D3'+
'\u06D5\u06E5-\u06E6\u0905-\u0939\u093D\u0958-\u0961\u0985-\u098C'+
'\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09DC-'+
'\u09DD\u09DF-\u09E1\u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-'+
'\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-'+
'\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8B\u0A8D\u0A8F-\u0A91\u0A93-'+
'\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AE0\u0B05-'+
'\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B36-'+
'\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B85-\u0B8A\u0B8E-\u0B90'+
'\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-'+
'\u0BAA\u0BAE-\u0BB5\u0BB7-\u0BB9\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-'+
'\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C60-\u0C61\u0C85-\u0C8C\u0C8E-'+
'\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CDE\u0CE0-\u0CE1'+
'\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D60-\u0D61'+
'\u0E01-\u0E2E\u0E30\u0E32-\u0E33\u0E40-\u0E45\u0E81-\u0E82\u0E84'+
'\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3'+
'\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EAE\u0EB0\u0EB2-\u0EB3\u0EBD'+
'\u0EC0-\u0EC4\u0F40-\u0F47\u0F49-\u0F69\u10A0-\u10C5\u10D0-\u10F6'+
'\u1100\u1102-\u1103\u1105-\u1107\u1109\u110B-\u110C\u110E-\u1112'+
'\u113C\u113E\u1140\u114C\u114E\u1150\u1154-\u1155\u1159\u115F-'+
'\u1161\u1163\u1165\u1167\u1169\u116D-\u116E\u1172-\u1173\u1175'+
'\u119E\u11A8\u11AB\u11AE-\u11AF\u11B7-\u11B8\u11BA\u11BC-\u11C2'+
'\u11EB\u11F0\u11F9\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15\u1F18-'+
'\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D'+
'\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-'+
'\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-'+
'\u1FFC\u2126\u212A-\u212B\u212E\u2180-\u2182\u3041-\u3094\u30A1-'+
'\u30FA\u3105-\u312C\uAC00-\uD7A3';
var DIGIT = '\u0030-\u0039'; // 0-9
var HTML_TAG = new RegExp( '(<[^>]+>)' );
var TAG_REPLACE = new RegExp( DELIM_CHAR + '(\\d+)' + DELIM_CHAR );
var SINGLE_straight quotesQUOTES = new RegExp( '^\'|([^' + BASE_CHAR + DIGIT +
'])\'\\b', 'g' );
var APOSTROPHE = new RegExp( '\'', 'g' );
var DOUBLE_QUOTES = new RegExp( '"([^"]*)"', 'g' );
var EM_DASH = new RegExp( '--', 'g' );
var EN_DASH = new RegExp( ' +- +', 'g' );
var LARR = new RegExp( '<-{1,2}', 'g');
var RARR = new RegExp( '-{1,2}>', 'g');
while ( HTML_TAG.test( formatted ) ) {
temp[ count ] = RegExp.$1;
formatted = formatted.replace(RegExp.$1,DELIM_CHAR+count+DELIM_
CHAR);
count++;
}
formatted = formatted.replace( SINGLE_QUOTES, '$1‘' );
formatted = formatted.replace( APOSTROPHE, '’' );
formatted = formatted.replace( DOUBLE_QUOTES, '“$1”' );
formatted = formatted.replace( EM_DASH, '—' );
formatted = formatted.replace( EN_DASH, '–' );
formatted = formatted.replace( LARR , '←' );
formatted = formatted.replace( RARR , '→' );
while ( TAG_REPLACE.test( formatted ) ) {
formatted = formatted.replace(
DELIM_CHAR + RegExp.$1 + DELIM_CHAR, temp[ RegExp.$1 ]);
}
formatted=formatted.replace(/(\[(code|html|php)\])([\w\W\s]+?)(\[\/\2\
])/gi,
function(s){
s = s.replace(/‘/g, "'");
s = s.replace(/’/g, "'");
s = s.replace(/“/g, '"');
s = s.replace(/”/g, '"');
s = s.replace(/—/g, "--");
s = s.replace(/–/g, "-");
s = s.replace(/←/g, "<-");
s = s.replace(/→/g, "->");
return s;
});
return formatted;
};
window.addEventListener("submit", function(event) {
var elmForm = event.target;
var arTextareas = elmForm.getElementsByTagName('textarea');
for (var i = arTextareas.length - 1; i >= 0; i--) {
var elmTextarea = arTextareas[i];
elmTextarea.value = filterChars(elmTextarea.value);
}
}, true);
Running the Hack
After installing the user script (Tools → Install This User Script), go to http://snowwhite.it.brighton.ac.uk/~mas/mas/courses/html/try_pgt.html. Enter some text in the web form that includes straight quotes or apostrophes, as shown in Figure 3-11.
Click the View button, and the server will echo what you typed. The straight quotes and apostrophes have been transformed into curly quotes, as shown in Figure 3-12.
The script actually changes the value of the form field just before submitting, and Firefox remembers the new value when it caches the page. If you press the Back button after viewing the echoed output, you will see the actual changes that the script made. In this case, the script replaced the first quote mark with “ and the second quote mark with ”.
Add Dynamic Highlighting to Tables
Make tables easier to navigate by highlighting the current row.
"Add Stripes to Data Tables" [Hack #22] discusses the benefit of shading alternating rows in tables and lists. This hack is slightly different. It highlights rows in a table as you move your cursor over them. You can install both hacks at the same time. They do not conflict; in fact, they complement each other.
The Code
This user script runs on all pages. It iterates through all the table rows on the page and adds mouseover and mouseout event handlers to each row. On mouseover, it saves the background and foreground colors and then sets the background color to the highlight color (#88eecc, a super-intelligent shade of blue). On mouseout, it restores the original colors.
Save the following user script as tableruler.user.js:
// ==UserScript==
// @name Table Ruler
// @namespace http://diveintomark.org/projects/greasemonkey/
// @description highlight current row in appearanceweb sitesdata tablesdata tables
// @include *
// ==/UserScript==
var arTableRows = document.getElementsByTagName('tr');
for (var i = arTableRows.length - 1; i >= 0; i--) {
var elmRow = arTableRows[i];
var sBackgroundColor = elmRow.style.backgroundColor;
var sColor = elmRow.style.color;
elmRow.addEventListener('mouseover', function() {
this.style.backgroundColor = '#88eecc';
this.style.color = '#000';
}, true);
elmRow.addEventListener('mouseout', function() {
this.style.backgroundColor = sBackgroundColor;
this.style.color = sColor;
}, true);
}
Running the Hack
After installing the user script (Tools → Install This User Script), go to http://diveintomark.org/csshacks/. This is a table of hacks I devised to hide CSS rules from Safari. It is woefully out of date, but it is a nice example of a table and will serve as a good example here. Move your cursor around the table, and you will see the row beneath your cursor highlighted in blue, as shown in Figure 3-13.
Hacking the Hack
Currently, this hack highlights rows only. That's generally more useful than highlighting columns, and it's definitely easier due to the way HTML table markup is declared. But it can also be useful to highlight the current column. This will lead to a crosshair effect, where both the current row and column are highlighted as you move your cursor around the table.
HTML tables are laid out as cells within rows (<td> elements within <tr> elements). There's no such thing as a table column element. To highlight an entire column, we need to highlight each cell in the column. We can use the cellIndex attribute on a table cell to determine which column it's in.
To make this trick perform adequately, we'll need to do a little creative thinking. Rather than iterating through every table cell every time you move the cursor, iterate through all the cells once and build up a cross-reference array that lists which cells are in each column. Then, add a mouseover handler to each cell that gets the column index for that cell and checks the cross-reference array to find all the other cells in the same column. For computer science geeks, this reduces an O(N2) operation to O(N)—a huge improvement!
This script interacts badly with tableruler.user.js. Uninstall the Table ruler script from the Manage User Scripts dialog, and then save the following user script as tablecrosshair.user.js:
// ==UserScript==
// @name Table Crosshair
// @namespace http://diveintomark.org/projects/greasemonkey/
// @description highlight current row and column in data tables
// @include *
// ==/UserScript==
var arTableRows = document.getElementsByTagName('tr');
var arCellXref = new Array();
for (var i = arTableRows.length - 1; i >= 0; i--) {
var elmRow = arTableRows[i];
elmRow.addEventListener('mouseover', function() {
this._backgroundColor = this.style.backgroundColor;
this._color = this.style.color;
this.style.backgroundColor = '#88eecc';
this.style.color = '#000';
}, true);
elmRow.addEventListener('mouseout', function() {
this.style.backgroundColor = this._backgroundColor;
this.style.color = this._color;
}, true);
var arCells = elmRow.getElementsByTagName('td');
for (var j = arCells.length - 1; j >= 0; j--) {
var elmCell = arCells[j];
var iCellIndex = elmCell.cellIndex;
if (!(iCellIndex in arCellXref)) {
arCellXref[iCellIndex] = new Array();
}
arCellXref[iCellIndex].push(elmCell);
}
for (var j = arCells.length - 1; j >= 0; j--) {
var elmCell = arCells[j];
elmCell.addEventListener('mouseover', function() {
var iThisIndex = this.cellIndex;
for (var k = arCellXref[iThisIndex].length - 1; k >= 0; k--) {
var elm = arCellXref[iThisIndex][k];
elm.setAttribute('_backgroundColor', elm.style.
backgroundColor);
elm.setAttribute('_color', elm.style.color);
elm.style.backgroundColor = '#88eecc';
elm.style.color = '#000';
}
}, true);
elmCell.addEventListener('mouseout', function() {
var iThisIndex = this.cellIndex;
for (var k = arCellXref[iThisIndex].length - 1; k >= 0; k--) {
var elm = arCellXref[iThisIndex][k];
elm.style.backgroundColor = elm.getAttribute('_
backgroundColor');
elm.style.color = elm.getAttribute('_color');
}
}, true);
}
}
Now, go back to http://diveintomark.org/csshacks/ and move the cursor around the table. You'll see both the current row and column highlighted, creating a crosshair effect, as shown in Figure 3-14.
This is a great example of how publishing data on the Web can be more usable than printing it on paper. Imagine trying to navigate a printed table while holding two rulers at right angles!
Make Pop-up Titles Prettier
Spice up those boring link tool tips.
Many web pages include title attributes on links. When you hover over the link, the browser displays a tool tip that gives more information about the link. The font and color of the tool tip are determined by the theme settings of the underlying operating system. This means you have some control over what tool tips look like, but they'll still look pretty boring. This hack makes link tool tips sexier and more functional at the same time by replacing the tool tip with a translucent floating window that contains both the title and the link URL.
The Code
This user script runs on all pages. It works by finding all the links on the page (using the document.links collection) and adding mouseover, mouseout, focus, and blur events to each one. On mouseover or focus, it creates a wrapper <div> containing the link title and URL and positions it on the page just below the cursor. On mouseout or blur, it removes the <div> element. It sounds simple, but determining the exact position and dimensions of the <div> element is quite complicated, as you can see in the showNiceTitles function.
Also, I would like to point out that the nice title <div> is styled with rounded corners, using the -moz-border-radius CSS rule. It is also slightly translucent, thanks to the opacity rule.
Tip
The -moz-border-radius property is a Mozilla-specific extension to CSS. The upcoming CSS 3 specification will likely include a border-radius property. The Mozilla developers just couldn't wait to implement it, but because the syntax might change in the final CSS 3 specification, they implemented it as -moz-border-radius to avoid future compatibility problems.
Save the following user script as nicetitles.user.js:
// ==UserScript==
// @name Nice Titles
// @namespace http://www.kryogenix.org/code/
// @description render link titles with translucent floating window
// @include *
// ==/UserScript==
// based on code by Stuart Langridge
// and included here with his gracious permission
// http://www.kryogenix.org/code/browser/nicetitle/
var CURRENT_NICE_TITLE;
function makeNiceTitles() functionmakeNiceTitles() {
var arLinks = document.links;
for (var i = arLinks.length - 1; i >= 0; i--) {
var elmLink = arLinks[i];
if (elmLink.title) {
elmLink.setAttribute("nicetitle",elmLink.title);
elmLink.removeAttribute("title");
elmLink.addEventListener("mouseover",showNiceTitle,true);
elmLink.addEventListener("mouseout",hideNiceTitle,true);
elmLink.addEventListener("focus",showNiceTitle,true);
elmLink.addEventListener("blur",hideNiceTitle,true);
}
}
}
function findPosition( oLink ) {
if (oLink.offsetParent) {
for (var posX = 0, posY = 0; oLink.offsetParent;
oLink = oLink.offsetParent) {
posX += oLink.offsetLeft;
posY += oLink.offsetTop;
}
return [ posX, posY ];
} else {
return [ oLink.x, oLink.y ];
}
}
function web sitesappearancetitle attributes, modifyingshowNiceTitle(event) {
if (CURRENT_NICE_TITLE) {
hideNiceTitle(CURRENT_NICE_TITLE);
}
var elmTarget;
if (event && event.target) {
elmTarget = event.target;
}
if (!elmTarget) { return; }
if (elmTarget.nodeType == Node.TEXT_NODE) {
elmTarget = getParentElement(elmTarget);
}
if (!elmTarget) { return; }
attrNiceTitle = elmTarget.getAttribute("nicetitle");
if (!attrNiceTitle) { return; }
var elmWrapper = document.createElement("div");
elmWrapper.className = "nicetitle";
tnt = document.createTextNode(attrNiceTitle);
pat = document.createElement("p");
pat.className = "titletext";
pat.appendChild(tnt);
elmWrapper.appendChild(pat);
if (elmTarget.href) {
tnd = document.createTextNode(elmTarget.href);
pad = document.createElement("p");
pad.className = "destination";
pad.appendChild(tnd);
elmWrapper.appendChild(pad);
}
var h_pixels, t_pixels, w, h, mpos, mx, my;
STD_WIDTH = 300;
if (elmTarget.href) {
h = elmTarget.href.length;
} else { h = attrNiceTitle.length; }
if (attrNiceTitle.length) {
t = attrNiceTitle.length;
}
h_pixels = h*6; t_pixels = t*10;
if (h_pixels > STD_WIDTH) {
w = h_pixels;
} else if ((STD_WIDTH>t_pixels) && (t_pixels>h_pixels)) {
w = t_pixels;
} else if ((STD_WIDTH>t_pixels) && (h_pixels>t_pixels)) {
w = h_pixels;
} else {
w = STD_WIDTH;
}
elmWrapper.style.width = w + 'px';
mpos = findPosition(elmTarget);
mx = mpos[0];
my = mpos[1];
elmWrapper.style.left = (mx+15) + 'px';
elmWrapper.style.top = (my+35) + 'px';
if (window.innerWidth && ((mx+w) > window.innerWidth)) {
elmWrapper.style.left = (window.innerWidth - w - 25) + "px";
}
if (document.body.scrollWidth && ((mx+w)>document.body.scrollWidth)) {
elmWrapper.style.left = (document.body.scrollWidth - w - 25)+"px";
}
document.body.appendChild(elmWrapper);
CURRENT_NICE_web sitesappearancetitle attributes, modifyingTITLE = elmWrapper;
}
function hideNiceTitle(e) {
if (CURRENT_NICE_TITLE) {
document.body.removeChild(CURRENT_NICE_TITLE);
CURRENT_NICE_TITLE = null;
}
}
function getParentElement(node) {
while (node && (node.nodeType != Node.ELEMENT_NODE)) {
node = node.parentNode;
}
return node;
}
function getMousePosition(event) {
x = event.clientX + window.scrollX;
y = event.clientY + window.scrollY;
return [x,y];
}
function addGlobalStyle(css) {
var elmHead, elmStyle;
elmHead = document.getElementsByTagName('head')[0];
if (!elmHead) { return; }
elmStyle = document.createElement('style');
elmStyle.type = 'text/css';
elmStyle.innerHTML = css;
elmHead.appendChild(elmStyle);
}
addGlobalStyle(
'div.nicetitle {' +
' web sitesappearancetitle attributes, modifyingposition: absolute;' +
' padding: 4px;' +
' top: 0px;' +
' left: 0px;' +
' background-color: black;' +
' color: white;' +
' font-size: 13px;' +
' font-family: Verdana, Helvetica, Arial, sans-serif;' +
' width: 25em;' +
' font-weight: bold;' +
' -moz-border-radius: 12px !important;' +
' opacity: 0.75;' +
'}' +
'div.nicetitle p {' +
' margin: 0; padding: 0 3px;' +
'}' +
'div.nicetitle p.destination {' +
' font-size: 9px;' +
' text-align: left;' +
' padding-top: 3px;' +
'}');
window.addEventListener("load", makeNiceTitles, true);
Running the Hack
After installing the user script (Tools → Install This User Script), go to http://www.w3.org and hover your cursor over one of the links in the main navigation bar. Instead of the normal tool tip, you will see a rounded translucent tool tip with both the title and the URL of the link, as shown in Figure 3-15.
Hacking the Hack
Currently, this script checks only for links (using the document.links collection). But links aren't the only thing on web pages with titles. Virtually any element can have a title attribute. With a simple XPath query, we can find every element on the page with a title attribute and make a nice title out of it.
Replace the makeNiceTitles function with this version:
function makeNiceTitles() {
var snapTitles = document.evaluate("//*[@title]",
document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i=0; i<snapTitles.snapshotLength; i++) {
var elm = snapTitles.snapshotItem(i);
elm.setAttribute("nicetitle",elm.title);
elm.removeAttribute("title");
elm.addEventListener("mouseover",showNiceTitle,true);
elm.addEventListener("mouseout",hideNiceTitle,true);
elm.addEventListener("focus",showNiceTitle,true);
elm.addEventListener("blur",hideNiceTitle,true);
}
}
Now, go to the Greasemonkey home page at http://greasemonkey.mozdev.org/ and hover your cursor over the word Search in the pane on the left. This is an <h4> element with a title attribute, and when you hover your cursor over it, you'll see a nice title pop up, as shown in Figure 3-16.
This hack can be extended in other ways, too. Although few pages use it, HTML has tags for marking text as inserted or deleted: <ins> and <del>, respectively. These elements can have a datetime attribute to declare when the text was inserted or deleted. We can extend the makeNiceTitles function to display nice titles for inserted and deleted text.
Replace the makeNiceTitles function with this version:
function makeNiceTitles() {
var snapTitles = document.evaluate("//*[@title or @datetime]",
document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i=0; i<snapTitles.snapshotLength; i++) {
var elm = snapTitles.snapshotItem(i);
if (elm.dateTime) {
var sDate = elmIns.dateTime;
var dtIns = new Date(sDate.substring(0,4),
parseInt(sDate.substring(4,6)-1),
sDate.substring(6,8),
sDate.substring(9,11),
sDate.substring(11,13),
sDate.substring(13,15));
if (elm.nodeName == 'INS') {
elm.setAttribute("nicetitle",
"Inserted on " + dtIns.toString());
} else {
elm.setAttribute("nicetitle",
"Deleted on " + dtIns.toString());
}
} else {
elm.setAttribute("nicetitle",elm.title);
elm.removeAttribute("title");
}
elm.addEventListener("mouseover",showNiceTitle,true);
elm.addEventListener("mouseout",hideNiceTitle,true);
elm.addEventListener("focus",showNiceTitle,true);
elm.addEventListener("blur",hideNiceTitle,true);
}
}
On any site that properly uses the ins and del elements, you can hover over the inserted or deleted text to see the date and time it was modified. Three cheers for semantic markup!
















