JQuery textbox watermark revisited: making it a plugin 5

In a previous post I talked about how you can do a textbox watermark very easily using JQuery. I’m long overdue in showing how you can take that same JQuery code and make it into a simple plugin.

So why would we make it a plugin? A JQuery plugin gives you the chance to encapsulate JQuery functionality into a single, pluggable module which can then be activated by calling a single method (with optional settings passed to it). If you’ve used things like the JQuery Validation library, you’ve seen a bit of the power of plugins. They are not hard to write, and the official documentation on the JQuery site gives you enough information to get started.

We will start off with the same basic HTML text box that we had in the previous post:

<input type="text" id="textBox1" />

And some simple CSS to go with it:

.watermark { color: #CCC; font-style: italic; }

So, just to recap what we want to do: when our focus is off of the textbox, and the textbox is empty, we want to display the default text, which is “type here…” in a light gray color. When the focus is on the textbox and the default text is displayed, we want to clear the box and the styling. If they type something in the box and then click elsewhere, we want to keep the new text and not reactivate the watermark style.

Using the previous JQuery code I posted as a base, here is the plugin code with comments to explain how it works:

(function($) {
	// Create a plugin with the name 'watermark'
	$.fn.watermark = function() {
		// Loop through the elements in the selector that we call the plug-in on
		this.each(function() {
			// Apply defaults to the box
			$(this).addClass("watermark").val("type here...");

			// Apply our focus and blur events
			// When we click on the field, we expect it to clear the field and remove the watermark
			$(this).focus(function() {
				$(this).filter(function() {
					// Check to see if we have a blank field or the default text
					return $(this).val() === "" || $(this).val() === "type here...";
				}).val("").removeClass("watermark");
			});

			// When we click off of the field, we expect it to replace the watermark,
			// unless we have entered text
			$(this).blur(function() {
				$(this).filter(function() {
					// Check to see if the field is blank
					return $(this).val() === "";
				}).addClass("watermark").val("type here...");
			});
		});
	};
})(jQuery);

To apply the plugin to our text box above we can now simply do this:

$(function() {
	$("#textBox1").watermark();
});

Making it better

What we’ve done so far is fine if you’re only concerned with a specific case, but what if we wanted to use this same plugin for different default text scenarios, or with different CSS classes? As it turns out, this is not hard to do.

We’re going to add two simple options to our plugin, but leave our defaults as they currently are:

(function($) {
	// Create a plugin with the name 'watermark'
	$.fn.watermark = function(settings) {
		// Defaults
		var config = {
			watermarkClass: 'watermark',
			defaultText: 'type here...'
		};

		// merge the passed in settings with our default config
		if(settings) $.extend(config, settings);

		// Loop through the elements in the selector that we call the plug-in on
		this.each(function() {
			// Apply defaults to the box
			$(this).addClass(config.watermarkClass).val(config.defaultText);

			// Apply our focus and blur events
			// When we click on the field, we expect it to clear the field and remove the watermark
			$(this).focus(function() {
				$(this).filter(function() {
					// Check to see if we have a blank field or the default text
					return $(this).val() === "" || $(this).val() === config.defaultText;
				}).val("").removeClass(config.watermarkClass);
			});

			// When we click off of the field, we expect it to replace the watermark,
			// unless we have entered text
			$(this).blur(function() {
				$(this).filter(function() {
					// Check to see if the field is blank
					return $(this).val() === "";
				}).addClass(config.watermarkClass).val(config.defaultText);
			});
		});
	};
})(jQuery);

Now, we can customize how we use the plugin:

$(function() {
	$("#textBox1").watermark({
		watermarkClass: 'grayText',
		defaultText: 'search'
	});
});

Files

Useful links

Getting Cucumber, RSpec, and Mongoid to play nice 3

Out of the box, Cucumber is setup to work with Active Record and uses the database_cleaner gem to ensure that the database is clean on each run. database_cleaner does support a few object mappers, but Mongoid is not one of them. It is fairly simple, however, to get a Cucumber and RSpec setup that works with Mongoid, and ensures your test database is clean on each run.

First, if you need help getting Mongoid setup, I recommend you take a look at the documentation on the Mongoid website. That will get you up and running. Before making any other changes, go ahead and run the rspec and cucumber generators for Rails to get the necessary files. Once that is done, make sure that if you’re not using Active Record (and since you’re using Mongoid and MongoDB I’m assuming you’re not unless you have a second database), you exclude it in your config/environment.rb:

config/environment.rb
RAILS_GEM_VERSION = '2.3.5' unless defined? RAILS_GEM_VERSION
require File.join(File.dirname(__FILE__), 'boot')

Rails::Initializer.run do |config|
  config.gem "cucumber"
  config.gem "mongoid"

  config.frameworks -= [ :active_record ]
end

Next we will configure RSpec to clean our database before each spec is run. We can do this by configuring RSpec as follows:

spec/spec_helper.rb
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path(File.join(File.dirname(__FILE__),'..','config','environment'))
require 'spec/autorun'
require 'spec/rails'
require 'mongoid'

Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}

Spec::Runner.configure do |config|
  config.before(:each) do
    Mongoid.master.collections.each(&:drop)
  end
end

And, finally, we need to configure Cucumber. I will admit, I had to play around with this for a while to get the correct setup. At one point, I had a situation where my database was only being cleaned correctly every other run. This actually turned out to not be a problem with my configuration, but with my scenarios (I am still somewhat of a Cucumber noob). In a fashion similar to that of our RSpec configuration above, we will simply tell Cucumber to wipe out the collections before each scenario runs:

features/support/env.rb
ENV["RAILS_ENV"] ||= "cucumber"
require File.expand_path(File.dirname(__FILE__) + '/../../config/environment')

require 'cucumber/formatter/unicode'
require 'cucumber/rails/world'
require 'cucumber/web/tableish'
require 'webrat'
require 'webrat/core/matchers'
require 'spec/expectations'
require 'mongoid'

Webrat.configure do |config|
  config.mode = :rails
  config.open_error_files = false
end

Cucumber::Rails::World.use_transactional_fixtures = false

Before do
  Mongoid.master.collections.each(&:drop)
end

I am using “before” hooks on both RSpec and Cucumber. My reasoning behind this is to ensure that when something is running, it starts off with a clean slate. I could easily do an “after” and just have each one clean up after itself, but this way, if I just want to run a single spec or scenario and inspect the data in my test database afterwards, I have that option.

So far learning Cucumber and RSpec has been a great experience and both have really made Behavior Driven Development (BDD) click.

Useful links

How my true desire for improvement began 0

When I started to call myself a software developer, back in 2006, the title was a loose fit at best – I was very green, and I knew this, but I did my best not to let it discourage me too much.  One of my strongest assets has always been my ability to learn and adapt based on observation and practice, and to do so quickly.  Usually the process begins when I know there is a better way to do something, and set out in search of it.  When I find something of interest, I start to read into it, and usually that becomes the rabbit hole that sets me off on an adventure.

When I started doing research on NHibernate in late 2007 after being discouraged with traditional data access and drag-and-drop designers, I never would have expected the adventure it led me on.

It all started when I began to look at a proprietary code generator and how it worked, and the massive amounts of code it spit out based on the database schema.  For a couple of weeks I made my best attempt to dig in and find out as much as I could without having access to the actual source code for the generator, to see how the data access was done.  After all, very little had to be done on the part of the developers using the tool – all that had to be done was make the modifications to the schema and run the tool.

Why did I feel like I needed to understand this data access?  Well, for one, just to make my life easier using the tool and understanding how it worked.  Secondly, I wanted to see if there was anything I could adapt and use for my own applications, just in the way of general data access strategy.  I became frustrated, and soon gave up on using anything that this application was generating because it seemed too complex for just data access.  I wanted something better.

I still remember the first blog posts I read about NHibernate.  titled “A Journey with Domain Driven Design (and NHibernate)” over on Ben Scheirman’s blog.  Many of the entries in the series were dated back in 2006, but they were still applicable enough for me to get my feet wet using NHibernate.  I printed out hard copies of the whole article series and read them several times, wrote many several applications, and started to get the hang of things.  The POCO (Plain-Old-CLR-Objects) and Persistence Ignorance approach to data access really resonated with me, and I wanted more.  After reading through the series, however, I got many more ideas in my head, and became very thirsty for knowledge.

Through the article I became exposed to Test Driven Development (TDD) and Domain Driven Design (DDD).  The more I read, the more I wanted to learn.  When I came across the ALT.NET groups, practices, and ideas, I found that there were many others out there who shared some of the same frustrations I had with the Microsoft-centric nature of .NET development.  I was able to learn things like Dependency Injection/Inversion of Control and the SOLID Principles.  I am not just plugging buzzwords for the sake of plugging, all of these things I have learned in the last almost two years have made me a much better software developer.

Even better is that there are a lot of really smart developers out there who have done lots of work in the public arena to get these ideas circulating, and are very much the developers who I admire.  They make me, as a software developer who is still early in my career, want to work harder to achieve the same excellence.  Some of them are listed in my Blogroll, although looking at it now I realize I still have more to add.

I really do feel that we never stop learning, regardless of what career path we choose.  I know I have not and never will.  I am happy to say that I have continued to venture outside my comfort zone, trying new languages and new platforms for software development.  My craft is to develop great software, and maybe learn a thing or two while doing it to make the next project that much better.

« Previous PageNext Page »