Long-holding shortcut keys in OS X (and other cool combos)

[tweetmeme service=bit.ly]
I switched to Mac around 10 years ago and have picked up a variety of shortcuts over the years to streamline my workflow. But, something I only found out about this weekend was the “long-hold”.

What’s a long-hold?
Hit your Dashboard key. Your dashboard appears. Hit the key again and it disappears in an equally pleasing manner. Now try it this way: press your Dashboard key (and keep it held), your Dashboard will appear. To make it disappear simply let go of the key!

Where else does this awesome trick work?
Well, since I’ve only just found this out, I’ve not had a huge chance to uncover many goodies, but here’s what I’ve found so far. Leave a comment if you know of any others.

  • Dashboard
  • Exposé
  • Dictionary lookup (Ctrl+Cmd+D)

What other shortcut modifiers don’t I know about?
Clicking menu items, or shortcut keys whilst holding shift or option can do slightly different things, here’s a list of things I’ve discovered, some of which are useful, some not so much:

  • Hold Shift while changing volume to suppress the system click
  • Hold Option while clicking on the volume or wireless menu items changes the menu items
  • Hold Shift while opening Spaces, Dashboard or Exposé – I’ll let you try that out out (very cool)
  • Holding Command allows you to reorder icons on the menu bar by dragging them about
  • When using Command+Tab to switch windows, holding shift as well allows you to move right to left
  • For fine-grain control when dragging scrollbars, hold Option to scroll one pixel at a time
  • Want to drag a window in the background without activating it? Hold Cmd

Those were cool, are there any other shortcuts that need a place in my life?
Yep. Some of these are amazingly handy, some not so much!

  • I find windows users complain a lot about the lack of Home and End keys which on a PC keyboard are tucked away somewhere 3 feet to the right of the keyboard.  Life with a Mac is easier as these keys are under your fingers already: Ctrl+A and Ctrl+E (should be familiar to you unix peeps) and Cmd+Left and Cmd+Right for beginning and end of line.
  • Here’s a couple for Safari that I use a lot: Ctrl+L puts focus in the Location bar and Ctrl+Shift+L will open a new window having Googled for the highlighted word 🙂
  • I covered it earlier, but to bring up the dictionary definition of a word in a Coco app: highlight the word and press Ctrl+Cmd+D
  • Ever wanted to blank the screen? Ctrl+Shift+Eject will turn brightness down to nothing.  Moving the mouse or typing will bring it back.  This works with long-holding too.
  • I’ve also noticed Windows users are very familiar with pressing Enter or Delete when highlighting icons in Finder.  On a Mac, the keys you want are Cmd+O to Open the item and Cmd+Delete to move it to the trash.

Please leave a comment if there are any other hidden gems you want to share with the world.

onbeforeunload event and differences between browsers

[tweetmeme service=bit.ly]
The [recently in webkit] supported onbeforeunload window event can be very useful to inform the user if they are about to loose some data which they haven’t saved yet etc. So, in your method that the event calls you will probably have some logic to decide if the method should return or not (returning a string from the method causes the message to be displayed to the user).

This is all very well and good, but we came across an instance where outside of this method we wanted to set a variable to stop the message, request a new window.location (to download an file) and then turn the variable on again. The code looked a little like this:

window.shouldWarnTheUser = false;
window.location = '/download/my.file';
window.shouldWarnTheUser = true;

And the onbeforeunload callback looked a bit like this:

window.onbeforeunload = function() {
  if (window.shouldWarnTheUser) {
    return "Some warning!";
  }
}

This gave us different results between different browsers and took some time for us to figure out why.

Consider the following example:

window.onbeforeunload = function() {
  console.log("3");
}

console.log("1");
window.location = '/download/my.file';
console.log("2");

Question:

What order do the numbers get logged out?

Answer:

  • Webkit: 1, 2, 3. (Safari 4.0 and Chrome 4.0 on OS X 10.6.2)
  • Firefox: 1, 3, 2. (FF 3.5 on OS X 10.6.2)
  • Both understandable depending on how you think the events are fired

  • Internet Exploder: 3, 1, 2. – What?! (IE 8 on Vista)

Keeping custom Rails model validations DRY

[tweetmeme]
There are a few blog posts on writing model validators – this is another one which follows the DRY (don’t repeat yourself) philosophy.

Suppose you want to check that an attribute value is present in a table of tags in our database. Now, this isn’t a particularly good use case as you could use validates_inclusion_of, but lets assume that we want to DRY that up a bit.

Let’s write the validation to make the following work:

validates_inclusion_in_tags :primary_tag, :secondary_tag

The validation method would look a little bit like this:

def validates_inclusion_in_tags(*attr_names)
  validates_each attr_names do |record, attr_name, value|
    acceptable_values = Tag.find.all.map{ |obj| obj.name }
    record.errors.add(attr_name, "does not match any available values") unless acceptable_values.include?(value)
  end
end

What’s going on here? Firstly we attach our custom validation to the model using validates_each. This runs the block for each of the attribute names (symbols) in the attr_names array (note that we have used the splat operator in our argument list to flatten the arguments into an array so we can pass a list of attribute to validate). The validation block is passed three arguments: the record being validated, the attribute name (as a symbol) and the current value of the attribute that is being validated. We then execute a find on our Tag model to get an array of AR records which we then map to extract the name of each object into an array. Finally we associate an error to the attribute that we are validating if its value is not in the list of acceptable values we have created.

To make the validation method a little more configurable and a little more generic we can allow the validation method to be called with a hash of options. Maybe we want to define a different error message, supply a different model to validate against, or supply a different attribute to be selected from our model. This can be easily achieved like so:

def validates_inclusion_in_model(*attr_names)
  # Set up any default configuration options and merge on any passed to the validation
  configuration = {
    :message => "does not match any available values",
    :model => Tag,
    :model_attribute => :name,
  }.merge(attr_names.extract_options!)

  validates_each attr_names do |record, attr_name, value|
    acceptable_values = configuration[:model].find.all.map{ |obj| obj.send configuration[:model_attribute] }
    record.errors.add(attr_name, configuration[:message]) unless acceptable_values.include?(value)
  end
end

You’ll note that the default configuration options are merged with the configuration options that were passed into the validation method. This (very common) technique makes use of the extract_options! method which returns either the last item of an array if it is a Hash or an empty Hash.

OK, that’s all good – what’s the best way to get this method available to our models. The best ways are as follows: Either create a validators.rb file in config/initializers and open up the ActiveRecord::Base class like so:

ActiveRecord::Base.class_eval do
  # define your validation methods here
end

Or, the way I prefer is to to create a validations.rb file in your lib directory and use the following code:

module Validations
  # Extend the caller with the ClassMethods module
  def self.included(base) # :nodoc:
    base.extend ClassMethods
  end

  module ClassMethods
    # define your validation methods here
  end
end

Now you can pick and chose which validations are available to your models rather than extend every model that inherits from AR Base by using:

class Person < ActiveRecord::Base
  include Validations
end

Of course, if all of your AR models inherit from a shared base class which in turn inherits from AR Base, then you could also include the validations model at your shared base model level to give all your models access to your new validations.

Note on performance

The validation method here would load all the Tag records into AR objects in memory just to throw them away again. Also, this problem is exacerbated if you were to compare two attributes against the list as the objects are loaded for each call into validates_each.