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 (18 Feb 2012): I’ve added some example code that shows the modifications needed to allow this to work in Greasemonkey. I’ve also added back the comments above the “full monty” code that I’d removed, which link to the Gmail Greasemonkey API docs.
Here’s an update to my Gmail label column resizing code that fixes the issue of not being able to select any page content.
The first version didn’t have this problem as I’d not put any restrictions in place. Unfortunately this caused content to become selected while dragging the grab bar, which looked quite ugly. The second version permanently disabled the ability to select content from the right-hand column… not exactly a desirable feature if you needed to copy something from an email!
This update gives the best of both worlds: content is selectable at all times, except when the grab bar is being dragged.
As with the last few posts, there are three versions: a bookmarklet with all unnecessary whitespace and punctuation removed, a slightly larger version with some formatting, and lastly a fully commented version for those who want to understand how it works.
From the many emails I’ve received, I realised that not everyone necessarily knows how to use a bookmarklet, and so I’ve included some instructions at the end of this post detailing how to run the code in various web browsers. If after reading them you’re still having trouble getting the code to work, let me know.
Thanks for all the feedback you’ve been sending. It’s great to know that so many people are finding this useful!
The code
Here’s the no-whitespace bookmarklet version. Unlike the previous posts, I’ve included the “javascript:” prefix to make it even easier to use.
javascript: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',k=function(){r[W]=b[o]-l[o]+P};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')+2]=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,0);g[n]('mousedown',function(e){e.which==1&&(x=l[o]-e.pageX,r[U+3]=r[U],r[U]+=r[U+2])},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(e){e.which==1&&(x=0,r[U]=r[U+3],k())},0)})
This update has pushed the size up a bit more than the last update did (it’s now 961 bytes)… but all the browsers I’ve tested this in seemed to handle bookmarklets of this size without any problems.
Here’s 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', k=function() { r[W]=b[o]-l[o]+P; }; 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')+2]=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, 0); g[n]('mousedown', function(e) { e.which==1 && (x=l[o]-e.pageX, r[U+3]=r[U], r[U]+=r[U+2]); }, 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(e) { e.which==1 && (x=0, r[U]=r[U+3], k()); }, 0); });
And the full monty:
// 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' k this is the function that sets the width of the right-hand column 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', k=function() { r[W]=b[o]-l[o]+P; }; // 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 the 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 inline style from the Compose button. At present, the only inline style is a browser-specific user-select style used to prevent text selection. // This is applied to the right-hand column while dragging is underway to stop the text being selected. It is removed afterwards so that text can be copied. // As I wanted to keep the code size as small as possible, it was easier to let Google do the browser sniffing and copy their style rather than deal with the vendor prefix approach. // Store this in a property called 'cssText2' of the right-hand column's style object r[(U='cssText')+2]=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, 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 // Save a copy of the existing right-hand column style so we can restore it later, then disable text selection e.which==1 && (x=l[o]-e.pageX, r[U+3]=r[U], r[U]+=r[U+2]); }, 0); d[n]('mousemove', function(e) { // If we have the mouse button pressed, work out the x delta then 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(e) { // If LMB is released, stop the mousemove code from running and restore the right-hand column style // k() needs to be called again, as restoring the style alters the width e.which==1 && (x=0, r[U]=r[U+3], k()); }, 0); });
How to run the code
I’ll start by showing how to run the bookmarklet version of the code, followed by a quick overview of the one modification needed to run the code using Greasemonkey.
Running the code as a bookmarklet
I know that many people will be unfamiliar with the term "bookmarklet", or what they do. In a nutshell, they’re small pieces of code that are usually designed to add extra functionality to a web page, and they are normally run (activated) by clicking a link on a page or in your browser’s bookmarks / favourites list. If you want to find out more, take some time to read the Wikipedia article that covers the topic in great detail.
Note: these instructions are generic, as different web browsers have different interfaces and trying to detail every combination would make for a very long (and boring) read. If you have trouble getting the code to run, post a comment with the details of which web browser you’re using (including the version, if possible).
Regarding terminology, I’ll be referring to “bookmarks” throughout these instructions. If you’re more familiar with the term “favourites”, don’t worry – they’re exactly the same thing, so mentally substitute one word with the other
- Select / highlight the no-whitespace / bookmarklet version of the code
- Copy it to your clipboard. If you’re running Windows, pressing Ctrl+C should copy the selected text, while Mac users should find Cmd+C (⌘+C) works. You’ll probably also find that right-clicking the code box will give you a menu with an option to copy. If you’re running Linux, you already know how to copy to the clipboard, and if not, there’s probably a twenty page “man” document you can read to find out
- Now that you’ve got the code copied to your clipboard, you need to create a new bookmark to paste it into. How you do this depends on your web browser, but normally there will be a “Bookmarks” menu with an “Add” or “Manage” option
- You can set the name of the bookmark to be anything you like, but normally the name should be descriptive so you know what it is
- Set the URL / address of the bookmark to be the code that you copied earlier
- Save the bookmark
That’s half the job done… now to run the code:
- Load Gmail
- Wait for the page to finish loading
- Run the bookmark as you would any other bookmark (normally by selecting it from the “Bookmarks” menu / toolbar)
- If all has gone to plan, you should see two thin dotted lines between the label column and the main content area. They’re grey in colour, so if you’ve chosen a grey theme for Gmail, you might not see them. They look something like this (without the red border):
If you do not see two dotted lines in the red box above, then chances are the code will not work for you in Gmail. You might want to refer to the trouble-shooting steps below.
If you see the dotted lines in Gmail, then the code has loaded. You should be able to click and drag to change the width of the label column. If you do not see the lines in Gmail, or they are visible but are not draggable, here are some trouble-shooting steps you can try:
- Ensure you’re running a modern web browser; this code will not work on ancient browsers. If you’re running Internet Explorer, you need to be using at least version 9. Up-to-date versions of Firefox, Safari, Chrome, Opera, or any other modern browser should run the code without any problems, as it’s all fairly run-of-the-mill code and doesn’t use any non-standard or browser-specific routines (I tested the previous version of the code in Firefox v3.6 and it worked perfectly)
- Disable any Google Labs features you are using. Based upon the feedback left in the comments, it appears some of the Labs are not compatible with this code, or they change the layout of the page too much for the code to handle (for example, the Labs code that moves the chat section to the right-hand side of the page)
- While I’ve tested this code on several Gmail themes on the classic and new look, it’s possible that some themes may not be compatible. If you’re using anything other than one of the default themes, try switching back and see if things start working
- If you still cannot get the code to work, let me know. The more browsers I can get this to work in, the better. One exception is Internet Explorer: I’ve no intention of getting this to work with IE 6, 7, or 8. I still have flashbacks to the good old days when I had to debug IE 4 and Netscape 4 problems
Running the code from Greasemonkey
To get the code to work in Greasemonkey, the “gmonkey” variable must be prefixed with “unsafeWindow.”. Here’s the wrapper I used around the above “full monty” code that I’ve tested working in Fx 10.0.2:
// ==UserScript== // @name Gmail label column resizer // @namespace http://www.codecouch.com/ // @description User script that allows the labels column in Gmail to be resized // @include http://mail.google.com/* // @include https://mail.google.com/* // ==/UserScript== // Insert the code here, but don't forget to prefix "gmonkey" with "unsafeWindow." // The first line of code should now read: // unsafeWindow.gmonkey.load(2, function(o) {
Update: I’ve changed this code since first publishing it. I found that waiting for the window’s onload event would occasionally cause 2 grab bars to show up. If you’ve copied the old version that contains the following two lines, you should remove them:
window.addEventListener('load', function() { }, false);
Feel free to tinker and modify the code as you see fit, but please follow the terms of use and acknowledge where the code came from. I don’t expect you to fit the license into the bookmarklet, obviously :-). If you do come up with any improvements, I’d love to see them!
Known issues
Here’s a list of the issues I’m currently aware of:
- The grab bar disappears if the page is reloaded. If you find that it has vanished, try running the bookmarklet again. Alternatively, use a plugin like Greasemonkey to automatically run the code once the page has loaded
- When switching between pages / themes, the grab bar may end up in the wrong place. If you experience this, reload the page and run the bookmarklet again
- No minimum or maximum column size is enforced. If you drag a column too small, don’t expect things to look good
- The position of the grab bar isn’t persisted over a page reload. This is something I’m looking at adding to the next version
- Certain Gmail labs options or plugins are not compatible with this code. Maintaining a complete list is impossible, but almost certainly, anything that adds a right-hand column (e.g. the Gmail “Right-side chat” lab, Xobni “Smartr Inbox” plugin, etc.) will cause problems
Thanks again for all your feedback. If you’ve got any suggestions on how I can improve this, please let me know.