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:
- Compatibility
- The code
- Instructions for running the code as a bookmarklet
- Instructions for running the code as a user script in Greasemonkey
- Instructions for running the code as a user script in Chrome
- Known issues
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.
- Select / highlight the bookmarklet version of the code
- Copy the selected code to your clipboard
- Create a new bookmark (or edit an existing one)
- Set the name of the bookmark to be anything you like, e.g. “Gmail resizer”
- 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
- Save the bookmark
To run the code:
- Load Gmail
- Wait for the page to finish loading
- Visit the bookmark as you would any other bookmark (normally by selecting it from the “Bookmarks” menu or toolbar)
- 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:
- Copy the code below into a text editor
- Copy the “full monty” code above and paste it into the text editor after the line that reads “// Insert code here.”
- 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
- Drag the file into Chrome
- You should see a security message similar to this:
Click the “Continue” button - You should see another security message similar to this:
Click the “Add” button - You should see a confirmation message similar to this:
- 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