Welcome to Code Couch

Resizing the left-hand labels column in Gmail – a bug fix and extra user script compatibility

Posted by at 2:30am on May 9, 2012.

This is the latest version of my Gmail label column resizing code to address the Greasemonkey user script wrapper not working in Chrome, and to fix a minor z-index issue. I’ve also shrunk the bookmarklet version of the code by 7 bytes (because I could :-)), and have dropped the “some whitespace” version of the code from this post, as it served no real purpose.

Here’s a quick run-down of what’s in this post:

As usual, if you find bugs, can suggest improvements, or just have something to say about the code, please use the comment form at the end of the post. Use the code as you like, but please follow the terms of use and acknowledge where the code came from if you reproduce it anywhere.

Enjoy!

Compatibility

This code will not run in IE 8 or below. Other than that, I’ve tested the code on various versions of Chrome, Safari, Firefox, and Opera without any problems.

While I’ve implemented some detection for non-standard layouts (e.g. those created by some labs or plugins), it is impossible to guarantee 100% compatibility. I know the following labs worked fine during my testing and caused no issues:

  • Right-side chat
  • Navbar drag and drop
  • Move Icon Column
  • Google Docs gadget
  • Google Calendar gadget

The Xobni “Smartr Inbox” plugin is known to prevent my code from working.

I’ve had a 50% success rate running this as a user script in NinjaKit by using the Chrome user script wrapper and making minor modifications to the code. The other 50% was either nothing happening, or an error being thrown. I might look into this at some point, but it’s not high on my priority list at the moment.

The code

Here’s the bookmarklet version. If you’re unfamiliar with the concept of bookmarklets, read these instructions for running the code as a bookmarklet:

gmonkey.load(2,function(o){var i=o.getNavPaneElement(),l,r,c=o.getActiveViewElement(),d=c.ownerDocument,b=d.body,t=0,x,w,n,g,Q='querySelector',W='width',U='parentNode',P='px',S='style',k=function(){r[W]=b[o]-l[o]-c+P};while(c.compareDocumentPosition(i)&2)c=c[U];while(i[U]!=b)i=i[U];n=c[U='childNodes'],w=[n[0],n[1]];if(c.cells)for(o in w){for(l in r=w[o][U])if(parseInt(r[l][S][W])){w[o]=r[l];break}}l=w[0],r=w[1][S],c=b[o='offsetWidth']-l[o]-w[1][o];g=i.appendChild(d.createElement('div'));n=l[Q]('[role^=n]');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:1;left:'+(l[o]-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],e.preventDefault())},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” version with comments and formatting for easy reading:

// Use the Gmail Greasemonkey API to reliably find the 2 main elements (the left- and right-hand columns). You do not need Greasemonkey installed 
// to use this API. See http://code.google.com/p/gmail-greasemonkey/wiki/GmailGreasemonkey10API for details (note: the 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 should be no global namespace pollution
 
		i		initially points to the parent element of the left-hand nav, ultimately points to the parent element of the grab bar
		l		initially used as a loop counter, ultimately points to the element that is resized to change the left-hand column width
		r		initially used as a temporary loop variable, ultimately points to the style object of the element that is resized in the right-hand column
		c		initially points to the parent element of the active view, then parent element of all columns, then used to hold the width of columns 3-n (if present)
		d		this points to the document in the frame we use
		b		this points to the body
		t		this is used to calculate the top coordinate of the grab bar
		x		this holds the initial x delta between mouse click and left-hand column width
		w		initially used to point to the left-hand and main column elements, then during dragging, this holds the new width of the left-hand column
		n		initially holds pointers to all the columns elements, then the left-hand nav, then holds the string 'addEventListener'
		g		this points to the grab bar element
		Q		this holds the string 'querySelector'
		W		this holds the string 'width'
		U		used for many things, most notably holding the strings 'parentNode', 'childNodes', and 'cssText'
		P		this holds the string 'px'
		S		this holds the string 'style'
		k		this is the function that sets the width of the right-hand column
	*/		
 
	var i=o.getNavPaneElement(), l, r, c=o.getActiveViewElement(), d=c.ownerDocument, b=d.body, t=0, x, w, n, g, Q='querySelector', W='width', U='parentNode', P='px', S='style', k=function() { r[W]=b[o]-l[o]-c+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 n to hold pointers to all the column elements (even if more than 2 are present), and w to point to just the first 2
	n=c[U='childNodes'], w=[n[0], n[1]];
 
	// Detect if a table has been used for layout (this happens with some of the Gmail labs, e.g. Right-side chat). If present, find the first
	// element that is controlling the cell's width by finding the first element that has an explicit non-zero width style set. This isn't a
	// perfect detection method (working with all the child nodes might be better), but it's better than nothing, and works at the moment :-)
	if(c.cells)
		for(o in w) {						// Loop over the first two table cells
			for(l in r=w[o][U])				// Find all of the cell's child elements and loop over them
				if(parseInt(r[l][S][W])) {		// If the current child element has a non-zero style width explicitly set...
					w[o]=r[l];			// ... then assume it is the width-controlling element, and store in in the current item in w
					break;				// There's no point in continuing to check the children (doing a "When a stranger calls")
				}
		}
 
	// Update l and r to point to their intended elements, and determine how much space is taken up
	// by any other columns (if present) by subtracting the label & main columns widths from the body width
	l=w[0], r=w[1][S], c=b[o='offsetWidth']-l[o]-w[1][o];
 
	// Create the grab bar, and insert it into the page
	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.
	// This approach keeps the code size down, as the Gmail code handles the browser detection so I don't have to deal with vendor prefixes.
	// Store the inline style in a property called 'cssText2' of the right-hand column's style object
	n=l[Q]('[role^=n]');
	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:1;left:' + (l[o]-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 when the window is resized
	// 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 mouse event listeners. mousedown is added to the grab bar, mousemove and mouseup to the document
	g[n]('mousedown', function(e) {
		// Only trigger with the 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
		// Call e.preventDefault() to stop a native drag operation from starting (this happens a lot in Fx)
		e.which==1 && (x=l[o]-e.pageX, r[U+3]=r[U], r[U]+=r[U+2], e.preventDefault());
	}, 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 the 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 could alter the width
		e.which==1 && (x=0, r[U]=r[U+3], k());
	}, 0);
});

Running the code as a bookmarklet

Bookmarklets are small pieces of code that are usually designed to add extra functionality to a web page, and they are normally run (activated) by visiting a bookmark (or favourite) in your web browser. Here’s how to create a bookmarklet:

Note: these instructions are generic, as different browsers will have different ways of creating a bookmark. If you have trouble getting the code to run, post a comment with the details of the browser you’re using.

  1. Select / highlight the bookmarklet version of the code
  2. Copy the selected code to your clipboard
  3. Create a new bookmark (or edit an existing one)
  4. Set the name of the bookmark to be anything you like, e.g. “Gmail resizer”
  5. Set the URL / address of the bookmark to be the text “javascript:” (with the colon, without the quotes), followed immediately by the code you’ve just copied. Note: This first part of this step is very important: the code will not run without the “javascript:” prefix. I have not included the prefix in the code, as some browsers strip it when pasted as part of a URL for security reasons; typing the prefix has no such issue
  6. Save the bookmark

To run the code:

  1. Load Gmail
  2. Wait for the page to finish loading
  3. Visit the bookmark as you would any other bookmark (normally by selecting it from the “Bookmarks” menu or toolbar)
  4. If all has gone to plan, you should see two thin dashed lines between the label column and the main content area. The dashes are grey in colour, so may not be visible if your theme clashes. The should look like this (without the red border):

If you do not see two dotted lines in the red box above, try adjusting your screen’s brightness or contrast settings, and failing that, check the compatibility section to make sure you are using a supported browser.

Running the code as a user script in Greasemonkey

To get the code to work in Greasemonkey, the “gmonkey” variable reference must be prefixed with “unsafeWindow.”. Here’s the wrapper I used around the above “full monty” code:

// ==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) {

Running the code as a user script in Chrome

User scripts running in Chrome do not have access to all Greasemonkey features, including access to the “unsafeWindow” variable. Therefore, a different method is used to deliver the code.

To install the code into Chrome, follow these steps:

  1. Copy the code below into a text editor
  2. Copy the “full monty” code above and paste it into the text editor after the line that reads “// Insert code here.”
  3. Save the file with a name that ends in “.user.js”, for example, “chromeGmailResizer.user.js”. Note: the “.user.js” extension is important as tells Chrome that this code is to be installed as a user script
  4. Drag the file into Chrome
  5. You should see a security message similar to this:

    Click the “Continue” button
  6. You should see another security message similar to this:

    Click the “Add” button
  7. You should see a confirmation message similar to this:
  8. Load Gmail, and you should see the grab bar ready to go!
// ==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==
 
var loadGmailResizer = function() {
	if (typeof(gmonkey) != 'object' || !('load' in gmonkey)) return;
 
	// Insert code here.
	// No modification to the code is needed when running in Chrome, so the first line of code should still read:
	//
	// 	gmonkey.load(2, function(o) {
 
};
var d = document, s = d.createElement('script');
s.type = 'text/javascript';
s.textContent = '(' + loadGmailResizer.toString() + ')()';
d.body.appendChild(s);

Known issues

Here’s a list of all the issues I’m currently aware of (whether bugs or things I’d like to add):

  • 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
  • The grab bar disappears if the page is refreshed. Unless you use Greasemonkey, or run the code as a user script in Chrome, you’ll need to run the bookmarklet each time the page is refreshed
  • No minimum or maximum column size is enforced. If you drag a column too small, don’t expect things to look good :-)

Post to Twitter

Comments

There are 40 responses to this post.

  1. Thanks, that works great !! Do you think something similar could be done for Google Drive/docs ?
  2. Unfortunately not, as they don't expose the Greasemonkey API, and using any other method would be fragile at best due to the constant layout and code changes going on behind the scenes :-(
  3. great!! Thanks a lot!!
  4. Great tool, I have a slight issue where I get 2 drag bars when using the Chrome user script, and they disappear when I refresh the page.
  5. Hi Mark, I've seen the same behaviour myself, but not all the time. If I can track down what's causing it, I'll post an update. Failing that, I could modify the wrapper script to detect the presence of the bar and not run again if it finds it.
  6. Great, thanks!
  7. I followed the instructions to create the .user.js file. I dragged it to Chrome and answered the various security and confirmation dialogs. Rebooted Chrome and Gmail. Nothing! The file is listed in extensions - just doeasn't seem to work. Any help would be appreciated. Thanks. I'm using: Vista Ultimate Chrome 20.0.1132.47 m
  8. I started over and remade the file - it works, and it works well - finally I can see my labels. Now I've seen that it works, I think it should be made available as Chrome extension - there are a lot of people waiting for this. Thank you so much. Ray
  9. Great tool! Would really love a second grab bar (or a separate script) for changing the column width for the secondary inboxes when the "multiple inbox" lab is enabled and displayed to the right of the main inbox. Is it possible?
  10. I love u man!!Thnx a lot..(I wanted to resize this column for like ages)Cheers!!
  11. Hi Dan, Is this available in Gmail Labs as a quick install? Thanks Ian P.S. Why do Gmail not make the label width controllable, its useless as it stands now?
  12. Wow! I have to admit installing your Gmail label width code was a piece of cake. I thought it maybe yet another degree course in code, but nope, easy. Thanks a lot for the time investment. Shame on Gmail for not having this essential feature as default.
  13. Worked perfectly, thank you!
  14. Dan... you are my hero! I just moved to GMail from Outlook and the size of the Label Column has been KILLING me. You're solution has made me a very happy man! Many thanks.
  15. That is fantastic and if it is so easy - why don't Google build this sort of essential functionality in to the program for everyone to enjoy. Come on Google - sort it out please
  16. Hi Mark, I've written a fix for the multiple grab bars. If you read my latest post called "stopping multiple grab bars in Chrome", you should find the problem goes away. Let me know if you still see the issue.
  17. You have no idea how awesome this code is. Ok, maybe you do :) Thanks so much for this!
  18. Thanks! I have been cursing for a long time over the absence of this feature. I am using Chromium 18.0.1025.168 in Ubuntu 12.04 and it works great.
  19. Fantastic! Thank you so much.
  20. Thankyou very very much!! :-)
  21. I get the following error: Script: C:\USER\....\chromeGmailResizer.user.js Line: 5 Char: 1 Error: 'gmonkey' is undefined Code: 800A1391 Source: Microsoft JScript runtime error
  22. Two words: THANK YOU!
  23. Dan, Thank you for creating this "workaround". The inability to re-size the label column has bothered me ever since I began using GMail a few months ago. Sad that a company like Google would release a product without this feature as part of the native functions.
  24. Is it possible to get step by step instruction on how to make this Greasemonkey script work. I keep getting this pop-up message: "undefined If this problem persists, try reloading the page, or using the basic HTML version. Learn More." Thanks.)
  25. Has Chrome changed this? i get 'apps extensions scripts cannot be added from this website
  26. It looks like Google have introduced new security restrictions when installing extensions. If you enter chrome://extensions/ in the address bar to go to the extensions page, and then drag the script into there, it should install correctly. I'll update the instructions to reflect this.
  27. that worked, i have the 2 lines running down the left hand side but they won't resize?
  28. One word4 u : HERO !!!! Love this post. many thnx from Holland ;-)
  29. Thanks for this script and for the detailed instructions. The resizer works fine. I changed the splitter image to a transparent gif, though - my backgrounds are a bit darker most of the time: background:url(data:image/gif;base64,R0lGODlhAwAEAIAAAP///7+/vyH5BAEHAAAALAAAAAADAAQAAAIEDGCnVwA7) There's also a problem with the resize event, when the mail items change their height between "Compact" and "Cosy" setting (by this I mean the automatic resizing when the window gets too small/large enough for Gmail's taste). When that happens the splitter jumps to the right and the labels column loses its width. I did the Chrome User Script implementation. Hope you know what I mean and can help out. Thanks again for the effort.
  30. Hi, Been using extender in chrome for months successfully just recently broken in that when I drag labels column the email message on the right goes completed blank for all messages. Any ideas why? Thanks
  31. You made a the Apps mail users at my workplace here very happy with this labels resizer trick. Awesome hack, thank you.
  32. This is fantastic, Thanks!!
  33. i tried the bookmarklet method and it works. so i want to say "thank you". in fact i am so thankful that i want you to know, if i were a girl, i'd willingly have as many of your babies as you want. thank you, thank you, thank you!
  34. All: For anyone finding that this code no longer works since late March 2013, please read my latest post dated 31st March 2013. It offers an updated wrapper that works with the latest Gmail changes.
  35. THANK YOU SO MUCH!! It's the little things in life that make me happy, and this is a huge 'little thing.' Any chance you are working on a bookmarklet to allow text wrapping for Google Calendar? Jennifer
  36. First, I think I love you. I can't wait to tweak it to create the same kind of grab bar for the right column. Second, I made the grab bar wider (10px) because it's difficult to see in such a light color. I'd really prefer to replace the image with a background color of my choosing, but I can't seem to make it work without the background image. Can you help? If that's not possible, how can I construct my own "data" background image? Last, the only thing that could make this wondrous script any better would be to add a toggle button to allow me to collapse/expand to my desired width with one click. Even better, tie it to a keyboard shortcut so I can do it with a couple of keystrokes. Thank you SO much for this!
  37. Thank you! Anyway to make it load each time you access your gmail account? It's not a problem for me, but my co-workers......A permanent solution would work better for my colleagues in the office. Thanks again, much appreciated! RG
  38. @Gilk: If you are using Chrome, then use the userscript wrapper in my latest post dated April 7th 2013. If you are using Firefox, them install Greasemonkey and then install the userscript of the same date using that.
  39. @Frances: If you want to generate your own data URI from an image, search on Google for "data uri generator" and upload your image to get the encoded version back.
  40. THANK YOU SO MUCH. My company has just switched to Gmail, and after importing all my Outlook emails I was going mad not being able to see the full name of all my inbox subfolders. Your code has given me sanity again. Thank you very much! I shall spread the gospel!

Leave a reply

You must either log in or enter your name and email address to post a comment.

Your email address will not be published.

  • You do not need to log in to comment, but you can if you wish.
  • Log in