Update 3 (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 2 (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
Update 1 (Nov 2011): I’ve fixed the bug where resizing the browser window caused things to break. You’ll find the new code in this post
You can still go directly to the original code if you want to – it’s still at the bottom of this article.
I like Gmail, and I like it a lot, even though I was a very “late starter”, only really starting to use it at the start of this year. I was the social outcast of my geek friends, and as known as “the luddite” for my refusal to upgrade from Windows XP to Windows 7, or move away from Outlook and my beloved 1.3GB .PST file.
However, I’m far from a luddite: I’m a control freak, and my reasons for sticking with XP & Outlook were simply because I knew the software very well, and if something went wrong, I knew how to fix it. I’d also tried Vista for some weeks at work, and didn’t exactly find it was aiding my productivity (to put it nicely).
It was a similar story with Outlook – I knew that I had my mail locally, and could make my own backups, for free, and quickly. Gmail seemed to be a mystical entity that people talked about, but no-one really knew how safe their data was. I had to have faith that my data was safe, and that I could access it just as quickly, and not be slowed down by an interface less powerful than a native application (as a software developer / power user, I’m also a keyboard whore. I like shortcut keys, and I like intuitive interfaces).
I’m happy to say that since the start of the year, my I.T. environment has changed significantly – and for the better, I believe:
- I sold my Windows Mobile 6.5 phone (an HTC HD2), and purchased an iPhone 4S. Technically, the HD2 was – and still is, a damn good phone. With Windows Mobile 6.5 I could record calls, edit the registry, whatever – I was in control of my phone. However, the OS was rapidly losing supporters. For example, Moxier who for a long time had their WM6.5 Wallet software “coming soon” removed these statements from their blog.
- I deleted my Windows BootCamp partition and started booting into OSX[1]. I’ve owned this iMac for 3+ years, and it has served me very well, as a Windows PC. As you can imagine, I didn’t buy it for the Apple logo; the purchasing decision was made with my head, not my heart: At the time, it was the best computer I could get with a small footprint, and I have a very small desk. It also ran Windows, and I’ve been a Windows user since starting on v2.x at school. I still remember many happy hours typesetting on Aldus PageMaker on the school’s first 386, playing Sopwith Camel and The Game With No Name at CGA resolution, only a lot faster than the 286 machines. Wow – I feel old, and I’m not even 40!
- While I still have my 1.3GB PST file, I very rarely go near it. In fact, I very rarely kick open my Windows VM (it was odd that I used to run OSX in a VM on my iMac under Windows. It’s certainly easier to get Windows running in a VM on OSX!). I’ve made it read-only, and keep it only to reference client emails when I need to. This switch from Outlook coincided with the deletion of Windows and starting to use Gmail.
Gmail isn’t perfect
… well, what did you expect me to say? No software is bug-free (this I know: I seem to be a bug magnet). I’ve got issues with Gmail, but none of them are bad enough to stop me using it. My two biggest gripes with it are, in no particular order:
- The searching is nowhere near as good as I would expect, and this is not something that Google are shouting from the rooftops. A lot of the blurb tells you that it’s the “same powerful technology” that’s behind their search engine. Really? Give me substring matching, then I’ll believe you. My typical search on Google involves brackets, quotes, ANDs and ORs. While I can use these, not having substring / partial matching is a very big negative point.
- The left-hand column is not resizable. It was not resizable on the old interface, nor the old new look, nor the latest new look. As you can imagine, my label structure is quite specific. Don’t get me wrong – I know they’re not folders, I know they’re much better than folders, and my label structure is not even approaching a tenth of the size of my Outlook folder structure, as a lot of the good features in Gmail make it a lot easier to keep things simpler. The conversation view is a fantastic boost to productivity, yet it’s such a simple concept.
I wouldn’t mind if the left-hand column could resize within limits… anything more than the 176 pixels I’m currently allowed would be a bonus. At full-screen with 1920 pixels to fill, it looks very sad and lonely.
I cannot do anything about the search engine, other than report bugs if I find them. If I find bugs in Gmail, Google Docs, or any software from any company, I report them. I normally give very detailed information – after all, it’s in my best interests that the software I use gets better. I also read the help forums and see that many users share my frustrations of many pieces of software, Gmail being no exception.
Stop moaning and do something about it then!
Today I thought I’d do something about the left-hand labels column being a fixed size. Why? Because as a web developer, I could. That’s what I like about web applications – you can modify them as much as you like. And, after all, there’s no point in moaning about a problem that you are able to do something about.
I started out this morning with no real idea of where I was going, and finished an hour or so ago with a good first cut. It’s not perfect, but I’m very happy with what I’ve created – and without too much effort, which does make me wonder why Google have not implemented it already.
To say I had no real idea of where I was going isn’t strictly true. I did have a list of ideals that the code should adhere to:
- It had to run without any extra JavaScript framework. I like jQuery, and I use it often. However, the main reasons I use it are pretty much obsolete. All modern browsers support .querySelector() / .querySelectorAll(), and IE 9 works with addEventListener. No more testing for global event objects (yippee!). Anyway, I felt that this was such a simple task, it didn’t need a library weighing it down
- It had to run without Greasemonkey being installed. I know that Chrome will run user scripts without any extra extensions, but not everyone has Chrome, and I don’t believe that people should have to install an add-on to get behaviour than can be provided natively. I don’t run Greasemonkey scripts at all. I may, one day, but I’ve never yet had the need to.
- The code had to be small enough to use in a bookmarklet. I didn’t have an ideal size in mind, but needless to say, I’m very happy with how it turned out
- It had to run on modern browsers. Admittedly, I didn’t include IE in that list, but I’m slowly realising that IE 9 isn’t so bad (although I run OSX, so I don’t see it that often)
- It had to support at very least the new new look, and possibly the old new look Anything other than that was a bonus (the old old look, or any look with themes)
- It had to be as future-proof as possible. Google change their code frequently, and IDs and classes come and go. I wanted code that weathered these changes. Incidentally, I don’t follow the belief that Google go out of their way to make their code as hard to follow as possible. Why would they provide vast amounts of API documentation, scripting facilities with a visual IDE, etc, if they wanted to keep people from tinkering? Perhaps short class names have something to do with keeping the byte count down? Network traffic doesn’t come for free, and nor does storage space. If you believe Google want to stop you tinkering, you’d best stop reading now, lest your blood pressure raise too high
Compatibility
I can honestly say I was surprised by the outcome – and it’s not often I’m surprised by my own code. I tested it on the following browsers:
- IE 9 (Windows 7 VM)
- Chrome 17 dev stream (OSX)
- Fx 8 (OSX)
- Fx 3.6.24 (OSX)
- Opera 11.52 (OSX)
In all cases, I tested the “new new look” (‘new look’), the “old new look” (‘preview’), and the “old old look” (‘classic’). In all cases, the code “just worked”: I could resize and everything reflowed nicely. I could even resize the settings pages.
When I started coding this morning, I did not think that I would get it working in all the different UIs, let alone IE 9. Now you see why I was surprised with the results – and also with Google for not implementing this much asked-for feature.
The code
Earlier, I said I wanted to keep it small enough to use as a bookmarklet. I must admit, I’ve not actually tried it as a bookmarklet in any browser, as I’ve been using the various consoles to paste the JS code into. However, I think that 899 bytes would count as acceptable, wouldn’t you?
OK – it’s 899 bytes with all whitespace removed, and no comments. However, this is not a lesson in obfuscation, I simply wanted to keep the code size down for bookmarklets. I’ll also post a commented version (still pretty small, just over 3.5kB).
Before I post the code, let me paste in a quote that you read recently:
No software is bug-free
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
- Sometimes content will become selected during a drag.
- The bar doesn’t come back after a page reload
- If you resize the browser window, the calculations do not update so you may well see the right-hand column drop underneath the left-hand column
- No minimum size is enforced. If you drag too small, don’t expect things to look good
The good news is that if you see any visual glitches you can simply refresh the page and re-run the code. Or, edit the code and fix any bugs you find. Then share the fixes. That, ladies and gentlemen, is why I adore web development: it’s impossible to hide your code, and with the tools at our disposal getting better by the minute, there’s never been a better time to be a front-end developer (incidentally, if you are a front-end developer and you haven’t watched the Re-introduction to Chrome Developer Tools video on Paul Irish’s blog, I strongly suggest you spend 30 minutes watching it. You will learn something; probably lots of somethings
Here’s the no-whitespace version for those of you wanting to run it as a bookmarklet:
gmonkey.load(2,function(o){var l=i=o.getNavPaneElement(),c=r=o.getActiveViewElement(),d=c.ownerDocument,p='px',t=sx=0,dx,lw,rw,n,s,q,g;while(c.compareDocumentPosition(l)&2)c=c[u='parentNode'];while(i[u]!=d.body)i=i[u];l=(n=c.childNodes)[0],r=n[1],n=l[q='querySelector']('[role=navigation]'),b=n[q]('[role]'),g=i.appendChild(d.createElement('div'));r[s='style'][u='cssText']+=b[s][u];while(t+=n.offsetTop,n=n.offsetParent);g[s][u]='position:absolute;z-index:9;left:'+(l[o='offsetWidth']-5)+'px;top:'+t+'px;bottom:0;width:4px;cursor:ew-resize;cursor:col-resize;background:url(data:image/gif;base64,R0lGODlhAwAEAIAAAL+/v////yH5BAAAAAAALAAAAAADAAQAAAIERGKnVwA7)';g[n='addEventListener']('mousedown',function(e){e.which==1&&(sx=e.pageX,lw=l[o],rw=r[o])},0);d[n]('mousemove',function(e){sx&&(dx=e.pageX-sx,g[s].left=lw+dx-5+p,l[s].width=lw+dx+p,r[s].width=rw-dx+p)},0);d[n]('mouseup',function(){sx=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.
The same code, with a small amount of whitespace:
gmonkey.load(2,function(o){ var l=i=o.getNavPaneElement(),c=r=o.getActiveViewElement(),d=c.ownerDocument,p='px',t=sx=0,dx,lw,rw,n,s,q,g; while(c.compareDocumentPosition(l)&2)c=c[u='parentNode'];while(i[u]!=d.body)i=i[u]; l=(n=c.childNodes)[0],r=n[1],n=l[q='querySelector']('[role=navigation]'),b=n[q]('[role]'),g=i.appendChild(d.createElement('div')); r[s='style'][u='cssText']+=b[s][u]; while(t+=n.offsetTop,n=n.offsetParent); g[s][u]='position:absolute;z-index:9;left:'+(l[o='offsetWidth']-5)+'px;top:'+t+'px;bottom:0;width:4px;cursor:ew-resize;cursor:col-resize;background:url(data:image/gif;base64,R0lGODlhAwAEAIAAAL+/v////yH5BAAAAAAALAAAAAADAAQAAAIERGKnVwA7)'; g[n='addEventListener']('mousedown',function(e){e.which==1&&(sx=e.pageX,lw=l[o],rw=r[o])},0); d[n]('mousemove',function(e){sx&&(dx=e.pageX-sx,g[s].left=lw+dx-5+p,l[s].width=lw+dx+p,r[s].width=rw-dx+p)},0); d[n]('mouseup',function(){sx=0},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, I re-use some of these until they hold their final value. l this will end up pointing to the element that is resized to change the left-hand column width i this will end up pointing to the element that the grab bar is inserted into c this will end up pointing to the container that holds the left- and right- hand column elements l & r r this will end up pointing to the element that is resized to change the right-hand column width d this points to the document in the frame we use b this points to the 'Compose' button p this holds the string 'px' t this is used to calculate the top coordinate of the grab bar sx this holds the x coordinate of the mouse when dragging starts dx when dragging, this holds the difference between the start x coordinate and the current one lw this holds the initial width of the left-hand column (updated at the start of each drag) rw this holds the initial width of the right-hand column (updated at the start of each drag) n used for many things, most notably holding the string 'addEventListener' s this holds the string 'style' q this holds the string 'querySelector' g this points to the grab bar element u used for many things, most notably holding the strings 'parentNode' and 'cssText' */ var l=i=o.getNavPaneElement(), c=r=o.getActiveViewElement(), d=c.ownerDocument, p='px', t=sx=0, dx, lw, rw, n, s, q, g; // Update c and i to point to their intended elements while(c.compareDocumentPosition(l)&2) c=c[u='parentNode']; while(i[u]!=d.body) i=i[u]; // Update l, r, and b to point to their intended elements. Create grab bar, and insert into DOM l=(n=c.childNodes)[0], r=n[1], n=l[q='querySelector']('[role=navigation]'), b=n[q]('[role]'), g=i.appendChild(d.createElement('div')); // Copy whatever x-user-select style is on the Compose button and add it to the RH column 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[s='style'][u='cssText'] += b[s][u]; // Calculate the cumulative offsetTop of for the top of the grab bar while(t+=n.offsetTop,n=n.offsetParent); // Style the grab bar g[s][u] = 'position:absolute;z-index:9;left:' + (l[o='offsetWidth']-5) + 'px;top:' + t + 'px;bottom:0;width:4px;cursor:ew-resize;cursor:col-resize;background:url(data:image/gif;base64,R0lGODlhAwAEAIAAAL+/v////yH5BAAAAAAALAAAAAADAAQAAAIERGKnVwA7)'; // Add event listeners. mousedown is added to the grab bar, mousemove and mouseup to the document g[n='addEventListener']('mousedown', function(e) { // Only trigger with LMB on grab bar. Stores initial x coord, and widths of left- and right- hand columns e.which==1 && (sx=e.pageX, lw=l[o], rw=r[o]) }, 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 sx && (dx=e.pageX-sx, g[s].left=lw+dx-5+p, l[s].width=lw+dx+p, r[s].width=rw-dx+p) }, 0); d[n]('mouseup', function() { // Stop the mousemove code from running when LMB is released sx=0 }, 0) })
How do I run it?
I’m going to follow the advice of a very good friend of mine, as much as it hurts my aesthetes to do so:
There’s no noun you can’t verb
The short answer: Google it.
The long answer: Google for ‘bookmarklet’
If you find bugs, make improvements, or whatever, do let me know. Other than that, use the code as you like, 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
Enjoy!