Update 2 (10 May 2012): I’ve added a new post that has all the various bug fixes to date as well as new code showing how to get the code working as a user script in Chrome as well as Greasemonkey.
Update 1 (13 Feb 2012): I’ve fixed the bug where selecting content on the page was disabled. You’ll find the new code in this post
The code in my original article had a few bugs that I wasn’t happy with, the main one being that if the browser window was resized, things went very pear-shaped. I’m happy to say that I’ve fixed this with a small update, along with a bit of global namespace pollution that I’d overlooked (oops!).
Text selection is next on my list: some ability to select text is lost, and and the inadvertent selection of text is possible.
For those of you running the bookmarklet version, don’t worry about the fix bloating the code: the size increased by 2 bytes, so at 901 bytes, it’s still pretty small.
Here’s the no-whitespace version:
gmonkey.load(2,function(o){var i=o.getNavPaneElement(),r,l,c=o.getActiveViewElement(),d=c.ownerDocument,b=d.body,g,t=0,x,w,n,Q='querySelector',W='width',U='parentNode',P='px',S='style';while(c.compareDocumentPosition(i)&2)c=c[U];while(i[U]!=b)i=i[U];l=(n=c.childNodes)[0],r=n[1][S],n=l[Q]('[role^=n]'),g=i.appendChild(d.createElement('div'));r[U='cssText']+=n[Q]('[role]')[S][U];while(t+=n.offsetTop,n=n.offsetParent);g[S][U]=W+':4px;position:absolute;z-index:9;left:'+(l[o='offsetWidth']-5)+'px;top:'+t+ 'px;bottom:0;cursor:ew-resize;cursor:col-resize;background:url(data:image/gif;base64,R0lGODlhAwAEAIAAAL+/v////yH5BAAAAAAALAAAAAADAAQAAAIERGKnVwA7)';top[n='addEventListener']('resize',k=function(){r[W]=b[o]-l[o]+P},0);g[n]('mousedown',function(e){e.which==1&&(x=l[o]-e.pageX)},0);d[n]('mousemove',function(e){x&&(w=e.pageX+x,l[S][W]=w+P,g[S].left=w-5+P,k())},0);d[n]('mouseup',function(){x=0},0)})
Note: If you are going to use this as a bookmarklet, you need to prefix the code with javascript: when adding it to the URL field.
Update: Here are some generic steps that you can try if you’re new to the concept of a bookmarklet.
- Select / highlight the initial “no whitespace” version of the code above
- Copy it to your clipboard
- Create a new bookmark (or edit an existing one)
- Set the URL to be “javascript:” (without the quotes), followed immediately by the code you’ve just copied
- Load Gmail
- Load the bookmark
The same code, with a small amount of whitespace:
gmonkey.load(2,function(o){ var i=o.getNavPaneElement(),r,l,c=o.getActiveViewElement(),d=c.ownerDocument,b=d.body,g,t=0,x,w,n,Q='querySelector',W='width',U='parentNode',P='px',S='style'; while(c.compareDocumentPosition(i)&2)c=c[U];while(i[U]!=b)i=i[U]; l=(n=c.childNodes)[0],r=n[1][S],n=l[Q]('[role^=n]'),g=i.appendChild(d.createElement('div')); r[U='cssText']+=n[Q]('[role]')[S][U];while(t+=n.offsetTop,n=n.offsetParent); g[S][U]=W+':4px;position:absolute;z-index:9;left:'+(l[o='offsetWidth']-5)+'px;top:'+t+ 'px;bottom:0;cursor:ew-resize;cursor:col-resize;background:url(data:image/gif;base64,R0lGODlhAwAEAIAAAL+/v////yH5BAAAAAAALAAAAAADAAQAAAIERGKnVwA7)'; top[n='addEventListener']('resize',k=function(){r[W]=b[o]-l[o]+P},0); g[n]('mousedown',function(e){e.which==1&&(x=l[o]-e.pageX)},0); d[n]('mousemove',function(e){x&&(w=e.pageX+x,l[S][W]=w+P,g[S].left=w-5+P,k())},0); d[n]('mouseup',function(){x=0},0) })
And the full monty, with some more updated comments :
// Use the Gmail Greasemonkey API to reliably find the 2 main elements (left- and right-hand columns). You do not need Greasemonkey installed to use the API. // See http://code.google.com/p/gmail-greasemonkey/wiki/GmailGreasemonkey10API for more details (note: documentation is out of date) // // Load the API. It will run our callback function when loaded, passing a GmailAPI object gmonkey.load(2, function(o) { /* Set up variables. To keep the code size down, some are re-used. There is no global namespace pollution at all. i this will end up pointing to the element that the grab bar is inserted into r this points to the style object of the element that is resized in the right-hand column l this will end up pointing to the element that is resized to change the left-hand column width c this will end up pointing to the container that holds the left- and right- hand column elements l & r d this points to the document in the frame we use b this points to the body g this points to the grab bar element t this is used initially to calculate the top coordinate of the grab bar, but is re-used to point to the resize function x this holds the initial x delta between mouse click and left-hand column width w during dragging, this holds the new width of the left-hand column n used for many things, most notably holding the string 'addEventListener' Q this holds the string 'querySelector' W this holds the string 'width' U used for many things, most notably holding the strings 'parentNode' and 'cssText' P this holds the string 'px' S this holds the string 'style' */ var i=o.getNavPaneElement(), r, l, c=o.getActiveViewElement(), d=c.ownerDocument, b=d.body, g, t=0, x, w, n, Q='querySelector', W='width', U='parentNode', P='px', S='style'; // Update c and i to point to their intended elements while(c.compareDocumentPosition(i)&2) c=c[U]; while(i[U]!=b) i=i[U]; // Update l, r, and n to point to their intended elements. Create grab bar, and insert into DOM l=(n=c.childNodes)[0], r=n[1][S], n=l[Q]('[role^=n]'), g=i.appendChild(d.createElement('div')); // Copy the browser-specific user-select style from the Compose button to the right-hand column in order to minimise text selection while dragging. // Copying the existing style seemed easier than adding from scratch... but as I didn't try, I can't say this with 100% certainty :-) r[U='cssText']+=n[Q]('[role]')[S][U]; // Calculate the top position for the grab bar while(t+=n.offsetTop,n=n.offsetParent); // Style the grab bar g[S][U] = W + ':4px;position:absolute;z-index:9;left:' + (l[o='offsetWidth']-5) + 'px;top:' + t + 'px;bottom:0;cursor:ew-resize;cursor:col-resize;background:url(data:image/gif;base64,R0lGODlhAwAEAIAAAL+/v////yH5BAAAAAAALAAAAAADAAQAAAIERGKnVwA7)'; // Add a resize event to the window to update the size of the right-hand column as the window is sized // While Gmail does this anyway, it doesn't take into account any size change to the left-hand column top[n='addEventListener']('resize',k=function() { r[W]=b[o]-l[o]+P }, 0); // Add event listeners. mousedown is added to the grab bar, mousemove and mouseup to the document g[n]('mousedown', function(e) { // Only trigger with LMB on grab bar. Stores initial x coord and width of left-hand column e.which==1 && (x=l[o]-e.pageX) }, 0); d[n]('mousemove', function(e) { // If we have the mouse button pressed, work out the x delta, update the column widths and the grab bar position x && (w=e.pageX+x, l[S][W]=w+P, g[S].left=w-5+P, k()) }, 0); d[n]('mouseup', function() { // Stop the mousemove code from running when LMB is released x=0 }, 0) })