jQuery.fn.labelize = function(){
	return this.each(function(){
		var $this = jQuery( this );

		// get the label
		var label = jQuery( 'label[for='+this.id+']' );

		// If no label, then return to avoid errors
		if( label.size( ) == 0 ){
			return;
		}

		// create wrapper element
		var wrapper = jQuery( '<div class="compactlabel-wrapper"></div>' );
		$this.wrap( wrapper );

		// move the label to before the the input
		$this.before( label );

		// create label backing
		var backing = jQuery( '<div class="compactlabel-label-backing"></div>' )
		.insertBefore( $this )
		.height( label.height( ) )
		.width( label.width( ) );

		// basic style information
		var labelOffset = label.position( ), inputOffset = $this.position( ), labelHeight = label.outerHeight( );

		// flag for if on or off
		var isHover = false, isFocus = false;

		// a jQuery object of the backing and label to animate both together
		var animatedElements = jQuery( backing.get( ) ).add( label.get( ) );


		// common functionality for hover on and focus on
		var on = function( event ){
			$this.parent( ).addClass( 'compactlabel-wrapper-on' );
			animatedElements.stop( ).animate( { "top": '-'+(labelHeight - inputOffset.top)+'px' } );
		};

		// common functionality for hover off and focus off
		var off = function( event ){
		if( isHover || isFocus ){
			return;
		}
		if( inputEmpty ){
			label.removeClass( 'compactlabel-hide' );
		} else {
			label.addClass( 'compactlabel-hide' );
		}

		if( !inputEmptyChange ){
			animatedElements.stop( );
		}

		animatedElements.animate( 
			{ "top": labelOffset.top + 'px' }, 
			function( ){ 
				$this.parent( ).removeClass( 'compactlabel-wrapper-on' ) 
			} );
		};

		var onHover = function( event ){
			isHover = true;
			on( event );
		};

		var offHover = function( event ){
			isHover = false;
			off( event );
		};

		var onFocus = function( event ){
			isFocus = true;
			on( event );
		};

		var offFocus = function( event ){
			isFocus = false;
			off( event );
		};

		// The pieces for keeping track if the value of the input is empty
		var inputEmpty = false, inputEmptyStart = false, inputEmptyChange = false;
		var checkInputEmpty = function( ){
			inputEmpty = $this.val( ) == '';
		};
		checkInputEmpty( );
		// ID for setTimeout for checkInputEmpty
		var checkInputEmptyFnID = null;

		if( !inputEmpty ){
			label.addClass( 'compactlabel-hide' );
		}

		// add the events
		// events to track if the value changed empty state	
		$this.focus( function( ){
			inputEmptyStart = inputEmpty;
			inputEmptyChange = false;
			checkInputEmptyFnID = setInterval( checkInputEmpty, 50 );
		} );

		$this.blur( function( ){
			clearInterval( checkInputEmptyFnID );
			checkInputEmpty( );
			inputEmptyChange = inputEmptyStart != inputEmpty;
		} );

		// animation events
		$this.focus( onFocus );
		$this.blur( offFocus );
		$this.hover( onHover, offHover );
		label.hover( onHover, offHover );
	});
}
