Tuesday, December 23, 2008

Merb and Rails Merge

The competing Ruby frameworks, Rails and Merb, have decided to join forces!

Here is the announcement from Rails creator David Heinemeier Hansson and the similar announcement from the Merb perspective from Yehuda Katz.

Keep up with all the latest Ruby and Rails news at Ruby Rails Review.

Tuesday, December 16, 2008

Ruby on Rails News Site

I haven't been very active on this blog recently, but I've been keeping plenty active on other endeavors. One of which is the Ruby Rails Review which my friend, Brian Burridge, announced last week.

As he mentioned in the announcement I created a very easy to use CMS tool which allows us to drag-and-drop the news stories all over the page for placement (or addition and removal), as well as a bookmarklet to simplify the process of adding new stories. I've very happy with how it turned out, unfortunately no one gets to see it but he and I.



Monday, September 15, 2008

Find me at:

Tuesday, September 09, 2008

Campfire activity notifier for Gnome, KDE, or console

The 37signals blog points out a simply Ruby script for KDE that will give you a visual notification when a new message is posted to a Campfire chat room.

I have modified that script to work for Gnome as well as KDE, and additionally a text console. I also made a few other additions such as:

  • Allow you to specify a specific chat room(s) as a command line argument

  • Multiple chats rooms can be monitored concurrently using threading

  • Added the Campfire logo to the notifications

  • Added some initial status messages to display login status, room topic, and current people in the room

  • Display of the last 3 messages of the current day's transcript so you know what's going on without need to login

  • Filter out ads



Here is my version of the script:

#!/usr/bin/env ruby

# == Synopsis
# Program to monitor a campfire chat room
# Modified from code provided at: http://www.snailbyte.com/2007/09/13/campfire-activity-notifier-for-kde/
#
# == Usage
# campfireMonitor [roomName] [roomName] ...
#
# == Author
# Steven Pothoven and Snailbyte Ltd.

require 'rubygems'
require 'tinder'
require "cgi"

class App
VERSION = '0.1.1'

def initialize arguments, stdin
# default settings
campfireSubdomain = 'mySubdomain'
campfireUsername = 'user@email.com'
campfirePassword = 'password'
roomNames = ['Room1', 'Room2']
if arguments.length > 0
roomNames = arguments
end

@ui = 'gnome'
@campfireIconPath = '/path/to/campfire-logo.png'
# define you favorite audio player and audio file here for audio notifications
@soundCommand = 'mplayer /usr/share/sounds/pop.wav'


@campfire = Tinder::Campfire.new campfireSubdomain
if @campfire.login campfireUsername, campfirePassword
alert nil, "CampfireMonitor", "Successfully logged in #{campfireUsername}"
@rooms = roomNames.collect { |roomName| @campfire.find_room_by_name roomName }
# remove any invalid rooms
@rooms.delete(nil);
@rooms.each do |room|
notify room, "CampfireMonitor", "Entered room."
notify room, "CampfireMonitor", "Topic is: #{room.topic}.".gsub("'","")
notify room, "CampfireMonitor", "Current users are: #{room.users}.".gsub("'","")
end
else
alert nil, "CampfireMonitor", "Failed to log in #{campfireUsername}"
end
end

# display notifcation message
def notify room, user, msg
if msg and msg.size > 0
msg = CGI.unescapeHTML(msg);
if @ui == 'kde'
system "dcop knotify default notify eventname \'#{user}\' \'#{''+room.name+': ' unless room.nil?} #{msg}\' '' '' 16 2"
elsif @ui == 'gnome'
system "notify-send -i #{@campfireIconPath} '#{user}' '#{''+room.name+': ' unless room.nil?} #{msg}'"
else
puts "#{room.name+':' unless room.nil?}#{user} - #{msg}"
end
end
end

# notify with sound
def alert room, user, msg
notify room, user, msg
if @soundCommand and @soundCommand.length > 0
system @soundCommand
end
end

def run
# first get any missed messages for today
threads = []
@rooms.each do |room|
thread = Thread.new do
room.transcript(Date.today).last(3).each do |m|
if !m.nil? and m[:message] and m[:message].size > 1
notify room, m[:person], m[:message].gsub("'","")
end
end
end
threads << thread
end
threads.each { |thread| thread.join}

# listen for more messages
threads = []
@rooms.each do |room|
thread = Thread.new do
notify room, "CampfireMonitor", "Waiting for messages..."
room.listen do |m|
if !m.nil? and m[:message].size > 1
unless m[:person] == "Ad"
alert room, m[:person], m[:message].gsub("'","")
end
end
end
end
threads << thread
end
threads.each { |thread| thread.join}

end
end

app = App.new(ARGV, STDIN)
app.run


I'm sure you can think of plenty more customizations such as command-line options for all the default settings, etc. But I wanted to keep it fairly simple.

One last thing, here is the cropped version of the Campfire logo I use for my notifications (campfire-logo.png):

Tuesday, September 02, 2008

Google Chrome (Chromium) JavaScript speed

Today Google announced and released their own web browser named Chrome (aka Chromium). They even made a nice comic book to describe the advantages of it.

One of the key features they advertise is faster JavaScript execution due to the V8 JavaScript VM. So, I decided to test it using my JS-BogoMips test.

Here are the results of my tests (Windows Vista on Intel® Core™2 Quad Q6600):
Firefox 3:
JS-BogoMIPS (3x) = (0.436779 + 0.446775 + 0.450888) / 3 = 0.444814

Chrome:
JS-BogoMIPS (3x) = (2.758552 + 2.884791 + 2.881541) / 3 = 2.841628

which is a 6.4x speed increase!

Wednesday, July 16, 2008

Steven Pothoven for President

Tuesday, May 06, 2008

Keystroke and Field Validation with JavaScript

Yesterday, I provided a simple tool to demonstrate the differences between keydown and keypress events. Today, I'm going to apply that knowledge toward making a lightweight form validation library. This library will prevent invalid characters from being entered in form input elements which is useful toward preventing malicious data from being entered (think XSS), as well as ensuring data integrity (data is in the valid format, all required fields present, etc.).

Sample Form:



Alpha
Alphanumeric*
Date
Decimal
Digit
Email
Hostname
IP address
Integer
Punctuation
Real number
Time
Whitespace
custom
type
format


* denotes required field.




The form above should only allow valid characters in each input field - this is your keystroke validation. Furthermore, for fields that require a specific format, when you leave a field with invalid formatted data it should provide an immediate visual indication of the error by setting a fieldWithErrors CSS class on invalid fields. Additionally, a fv:onInvalid event is fired on the invalid element to allow custom events handlers to provide additional actions. For example, enter a number in the date field and tab out. The field will be outlined in red from the fieldWithErrors CSS class and the message, "Date must be yyyy-mm-dd" will be shown by the handling of the fv:onInvalid event. Note: the CSS class will be removed automatically when the field is corrected, and a corresponding fv:onValid event will be fired to allow the custom code to clean up after itself.

Validation rules are defined as Regular Expressions. If you are not familiar with Regular Expressions here is a good reference. Additionally, many predefined regex rules for various validations can be found at the regex library.

Several common data types and formats are defined by default in the library, but it also provide the mechanism to add you own data types. In the custom entry, the type entry allows you to specify an on-the-fly keystroke validation regular expression, and the format entry allows you to specify an on-the-fly field format validation regular expression.

The Validate Form button demonstrates the action you would want to perform before a form submission. It invokes a validateAllFields function which just runs all the field validations (and invokes corresponding CSS rules and issues events). It also returns an array of the invalid field names in case you want to build a list of errors (like the Rails flash messages).


Usage Instructions

Sample Usage:

<script src="javascript/formValidation.js"></script>
<script>
var vf = new FormValidation();
vf.addValidationForField("date", "date", "date");
</script>

addValidationForField requires 3 parameters:

  1. The identifer of the field to add validation for

  2. The name of the data type to use for keystroke validation

  3. The name of the data format to use for field validation

In this sample all the names are the same, but they usually wouldn't be.

Just doing the above will prevent invalid characters from being entered in the data field and can indicate when the field is invalid.

When a field is changed, the format validation is run and will do two things.

  1. It will mark invalid fields are the fieldWithErrors CSS class. This can be used to appropriately highlight the error to the user. For example, the following CSS rule will outline the field in red:

    .fieldWithErrors {
    border: 3px solid red;
    }

  2. An event will also be issued in the field to indicate whether or not the field is valid. The events to watch for are fv:onInvalid and fv:onValid. These events can be used to hide/show error messages such the following which will display a pre-defined error message:

    $$('input').each(function(inputElem) {
    inputElem.observe("fv:onInvalid", function() {
    $(this.identify() + "-error").show();}.bind(inputElem));
    inputElem.observe("fv:onValid", function() {
    $(this.identify() + "-error").hide();}.bind(inputElem));
    });

    Or any number of other actions. For example, you may wish to display a popup error message such as those provided by Prototype Window or add messages to an error flash area as commonly done in Rails apps or toggle the submit button as enabled/disabled. Using these events allows you to add whatever actions you desire when the
    validation fails (or passes).



Download
formValidation.js

Monday, May 05, 2008

keydown vs. keypress (in JavaScript)

In my next post, I intend to discuss a validation library which performs keystroke and field level validation for HTML forms using JavaScript and regular expressions. However, before you can correctly understand how the validation works, as well as when you can test for various validation conditions, you have to recognize the different behaviors between the keydown and keypress events.

In the following text fields, type whatever you like and notice the different behaviors in the table below it.




EventcharCodekeyCodeDisplayShifted?
Keydown
Keypress


Some notable problem keys when dealing with JavaScript keystroke validation are the arrow keys and other editing keys (delete, home, end, insert) which should be allowed in order to edit a field value, however they can appear to be punctuation symbols which may not be desired in the field. Function keys and number pad keys are also problematic.



Here are some key observations regarding keypress events:

  • Firefox sends regular keys as charCodes and special keys as keyCodes

  • Safari sends both charCode and keyCode for regular keys but does not issue keypress events for special keys

  • Internet Explorer does not send charCodes, only keyCodes, and does not issue keypress events for special keys

  • Opera does not send charCodes, only keyCodes, but also issues keypress events for special keys making it impossible to distinguish between some keys such as '-' vs. Insert and '.' vs. Delete

  • Konqueror will send both charCode and keyCode for regular keys, but only keyCode for special keys


Friday, May 02, 2008

Bluetooth Proximity Monitor (improved)

About a month ago, a friend of mine pointed out a bluetooth proximity program for windows which would lock your desktop when you walked away by polling your mobile phone's proximity to your computer. I thought that sounded fun to try as I have a bluetooth enabled phone and laptop, however, I'm running Linux, not Windows. After a quick search on Google I found a Bluetooth Proximity Monitor script for Linux. It worked pretty well as it was, but I've made a few adjustments to it to improve it for my purposes.


  1. The original script is written for KDE or other window managers that use xscreensaver. Since I'm currently running in Gnome, I had to switch the commands to use gnome-screensaver instead.

  2. While changing the screensaver commands, I also added the ability to toggle my instant messenger (Pidgin) from 'available' to 'away'

  3. The original script has a single THRESHOLD value to toggle between being near and far. I decided I needed separate NEAR and FAR thresholds. This is due to the wide variance of proximity I can have while I'm sitting at my desk. Just turning in my chair so that my body was between the phone and the laptop could change my RSSI (proximity value) from -1 to -18, so I need a fairly high threshold to prevent that. On the other hand, setting the threshold high could allow my system to unlock when I'm still 30 feet away. So making separate thresholds allows the proximity monitor not to trigger just because I turned in my chair, but also not unlock until I've actually returned to my desk.

  4. With the higher away threshold, it's possible (though uncommon) to totally leave the bluetooth range before it triggers that you've gone away. So, I also added a little logic to trigger the change in proximity if you were previously in near proximity, but now your bluetooth can no longer be pinged (out of range, turned off, etc).

  5. Finally, I alter the proximity check interval depending on if you're near or far. The motivation for this was an attempt to reduce power demands a little in order to prolong the battery life. I haven't done any actual tests to determine if it helped or not. Basically, if you're at your desk, it only checks every 5 seconds to make sure you're still there. If you've walked away it switches to check every 2 seconds in order to be more responsive to when you return.



In the end, my version of the bluetooth proximity monitor script looks like:

#!/bin/sh

DEVICE="00:1A:8A:61:6C:FE"
CHECK_INTERVAL=5
NEAR_THRESHOLD=-1
FAR_THRESHOLD=-22
PID=0
START_CMD='/usr/bin/gnome-screensaver'
FAR_CMD='/usr/bin/gnome-screensaver-command -l && purple-remote setstatus?status=away '
NEAR_CMD='/usr/bin/gnome-screensaver-command -d && purple-remote setstatus?status=available'
HCITOOL="/usr/bin/hcitool"
DEBUG="/var/log/btproximity.log"

connected=0

function msg {
echo "$1" >> $DEBUG
}

function msgn {
echo -n "$1" >> $DEBUG
}

function check_connection {
connected=0;
found=0
for s in `$HCITOOL con`; do
if [[ "$s" == "$DEVICE" ]]; then
found=1;
fi
done
if [[ $found == 1 ]]; then
connected=1;
else
# msgn 'Attempting connection...'
if [ -z "`$HCITOOL cc $DEVICE 2>&1`" ]; then
# msg 'Connected.'
connected=1;
else
msg "ERROR: Could not connect to device $DEVICE."
fi
fi
}

function check_xscreensaver {
PID=`ps -C gnome-screensaver --no-heading | awk '{ print $1 }'`
if [ "$PID" == "" ]; then
$START_CMD &
fi
}

check_connection

while [[ $connected -eq 0 ]]; do
check_connection
sleep 1
done

name=`$HCITOOL name $DEVICE`
msg "Monitoring proximity of \"$name\" [$DEVICE]";

state="near"
while /bin/true; do

check_xscreensaver
check_connection

if [[ $connected -eq 1 ]]; then
rssi=`$HCITOOL rssi $DEVICE | sed -e 's/RSSI return value: //g'`

if (( $rssi <= $FAR_THRESHOLD )); then
if [[ "$state" == "near" ]]; then
msg "*** Device \"$name\" [$DEVICE] has left proximity (signal: $rssi)"
state="far"
$FAR_CMD > /dev/null 2>&1
CHECK_INTERVAL=2
fi
elif (( $rssi >= $NEAR_THRESHOLD )); then
if [[ "$state" == "far" ]]; then
msg "*** Device \"$name\" [$DEVICE] is within proximity (signal: $rssi)"
state="near"
$NEAR_CMD > /dev/null 2>&1
$START_CMD &
CHECK_INTERVAL=5
fi
fi
# msgn "RSSI = $rssi, "
elif [[ "$state" == "near" ]]; then
msg "*** Device \"$name\" [$DEVICE] is no longer detectable"
state="far"
$FAR_CMD > /dev/null 2>&1
CHECK_INTERVAL=2
fi
# msg "state = $state, PID = $PID, sleep = $CHECK_INTERVAL"

sleep $CHECK_INTERVAL
done

Wednesday, April 30, 2008

Using JavaScript in blogs without <script> support

At one point, Blogger (which is used for this blog) did not allow the <script> tag in blog entries so some people came up with interesting work-a-rounds. This post will showcase two techniques which work well together to provide JavaScript capabilities to your blogs. The first technique supports inline JavaScript with a make-shift <script> tag, while the second technique supports loading external JavaScript files specific to each blog entry which can still be useful even with <script> support.

These techniques work well together and almost need to be used together. The inline technique is only handy for adding a line or two of code because Blogger will try to be smart regarding the formatting of your blog entry and add <br/> tags between all your lines of code making the JavaScript interpreter choke. Additionally, Blogger also tries to format any < or > symbols as &lt; and &gt; which don't work too well in your code. The second technique allows you to load as much JavaScript code as you want, however you'll probably want to use the first technique to invoke the included JavaScript functions.

Testing inline JavaScript: failed. (wait for page to complete loading)

Technique 1 - Allowing Inline JavaScript

That previous test line tested my ability to add inline JavaScript to my blog following these instructions from ecmanaut.

To use JavaScript in this blog without any <br/> tags, I added the following code to my blogger template:

<script type='text/javascript'>
Event.observe(window, 'load', function() {
var c = document.getElementsByTagName('code'), s, i;
var junk = /^\s*\46lt;\133\133CDATA\133|]]\46gt;\s*$/g;
for( i=0; i &lt; c.length; i++ ) {
s = c[i].getAttribute('style') || '';
if( s.match( /display[\s:]+none/i ) ) {
eval( c[i].innerHTML.replace( junk, '' ) );
}
}
});
</script>

I made a couple of slight variations to the original code snippet on the instruction page. First of all, before I added that code, I also included prototype.js in my template to have access to it's handy extensions. I was then able to utilize protype's Event.observe function to execute this code when the page is loaded without messing up and other onload actions. Executing this code during the onload is recommended in the instructions, but not explained how to do it. Finally, since the template requires valid XML, the i < c.length line isn't valid until you change the < to &lt;

Then, in order to run this JavaScript test, I added this line to the bottom of this blog entry:

<code style="display:none"> <[[CDATA[ $('jsTestResult').innerHTML = 'passed'; ]]></code>

which changes the word failed to passed in my test line above.
<[[CDATA[ $('jsTestResult').innerHTML = 'passed'; ]]>

Technique 2 - Loading External JavaScript Files

In order to load JavaScript files, you could edit your Blogger template to include all the files you want as I did for prototype.js and script.aculo.us. However, then every view of your blog will load ALL your JavaScript files. That's fine if you have some JavaScript library that you want to be able to utilize in many/all your blog entries, but if you're adding entries like mine which are just showcasing various JavaScript techniques in separate blog entries, you only want to load the specific blog entry JavaScript if it's being viewed. Enter Dynamically Loading External JavaScript Files.

Right after the previously mentioned code, I added the dhtmlLoadScript function. However, since I have prototype.js loaded, I modified it a little bit. Here's my version:

function dhtmlLoadScript(url) {
var e = new Element("script", {src: url, type: "text/javascript"});
document.getElementsByTagName("head")[0].appendChild(e);
}

So, in any blog entry which I want to use an external JavaScript file I simply add the line:

<code style="display:none"> <[[CDATA[ dhtmlLoadScript('http://some.domain.com/jsfile.js'); ]]></code>

and I can use any of the included function. Furthermore, since the inline script won't be evaluated until the onLoad event it triggered, I don't have to worry about registering it for an onLoad event itself.

Friday, February 22, 2008

busyProcess: a visual indicator for long JavaScript tasks

Some time ago I wrote an article to demonstrate a simple method of displaying a processing/loading indicator for Ajax requests using prototype. That works great for Ajax requests, but let's assume you have some CPU intensive task that will be processed on the client side in JavaScript. This task will take several seconds to complete and you want to add some sort of visual indicator to the page while it's working so the user knows something is happening. enter the busyProcess function.

You could code your particular JavaScript task to display a processing indicator itself and remove it when it's done, however there are two problems you'll encounter. First, you'll probably never see the processing indicator since your task will immediately do its thing and not give control back to the browser in order to display the indicator before the task finishes and your code removed the indicator. Second, you'll end up adding the same code over and over around each function that may take a while to complete (DRY - Do not Repeat Yourself!).

busyProcess handles both of these situations. It is a flexible wrapper for any function passed in to it so that it can be used for any task, and it defers the execution of the function in order to allow the browser to render the visual indicator.

Example

Click this to be busy for 3 seconds.

Get to the code already!


/**
* busyProcess
*
* Add a busy indicator over an element while running the function it invokes
* @param {Object} element clicked on to invoke the task
* @param {Function} function to invoke after adding visual indicator
*/
function busyProcess(element, func) {
var busyIndicator = new Element("div", {id: "busy"});
busyIndicator.setStyle({zIndex: "100",
position: "absolute",
fontWeight:"bold",
height: "16px"});
Position.clone($(element), busyIndicator, {setWidth: false, setHeight: false});
busyIndicator.innerHTML = ' Processing...';
document.body.appendChild(busyIndicator);

// function needs to be deferred in order for the browser to render the
// busy indicator, but we need to wrap it in order to remove the busy indicator
// when it's done
func = func.wrap(
function(proceed) {
proceed();
busyIndicator.remove();
});
func.defer();
}



Ok, so how does it work?

The function takes in two arguments. The first is the element clicked on. This is used as the location of the popup processing indicator as the item clicked on is what invoked the action for the user. Since the prototype $() function is used, this can be an element id as well. The second argument is the function to invoke.

busyProcess starts by building the processing indicator. This can be customized to your preferences. Just be sure the indicator has a z-index greater than anything other layers on the screen, and that is has absolute positioning. I then utilize the prototype Position.clone to position the indicator over the element clicked on.

Now we get to the more interesting part. The indicator needs to go away when the task is done. But how do we know when it's done? You could make the task issue a custom event and register an event listener here to catch it in order to remove the processing indicator. However, then any function you use busyProcess with will need to issue that event when it's done. That could get messy, and easy to forget. So, instead we wrap the original function to have it remove the processing indicator when it's done.

So, how do you use this?

For a simple example, let's assume we want to sort some large table by different columns when the user clicks on little triangle icons indicating ascending or descending.
For simplicity I'm just going to add the code inline on an onclick attribute in the HTML. As a general practice I usually try to register event handlers in my JavaScript code so that the HTML has no JavaScript in it.


<td id="zipcode">Zip Code
<img src="images/sort-asc.gif" alt="ascending sort icon"
onclick="busyProcess(this, function() {sortTableBy(this.up('td').identify());}.bind(this));" />
</td>


I intentionally made that a little more complicated than it had to be just to demonstrate that you can use inline anonymous functions as well.

Optional (but useful) addition

In order to give a more obvious indication that work is being done and to prevent the user from clicking on anything else until it's done you may want to either darken or lighten the rest of the page. To do this, add another layer at the beginning of the busyProcess function like:

var lightenScreen = new Element("div", {id: "lightenScreen",
'class': "lightenBackground"});
document.body.appendChild(lightenScreen);

where the lightenBackground class is defined in CSS as:

.lightenBackground {
background-color: white;
opacity: 0.5; /* Safari, Opera */
-moz-opacity: 0.50; /* FireFox */
filter: alpha(opacity = 50); /* IE */
z-index: 20;
height: 100%;
width: 100%;
background-repeat: repeat;
position: fixed;
top: 0px;
left: 0px;
cursor: wait;
}

or the corresponding darkening version:

.darkenBackground {
background-color: black;
opacity: 0.2; /* Safari, Opera */
-moz-opacity: 0.20; /* FireFox */
filter: alpha(opacity = 20); /* IE */
z-index: 20;
height: 100%;
width: 100%;
background-repeat: repeat;
position: fixed;
top: 0px;
left: 0px;
cursor: wait;
}

Just be sure to remove this layer as part of the wrap by adding the line: lightenScreen.remove();

Friday, February 08, 2008

acts_as_conference - day 1

I attended the acts_as_conference conference on Ruby on Rails. It was put on by Rails for All whose founder, Robert Dempsey had previously given a free class on Ruby on Rails in Tampa, where I live, for free which was my first real exposure to RoR, and how I learned about this conference.

The conference was attended by around 150 people. There were some technical glitches, particularly with WiFi access for everyone, but overall it ran smoothly.

Here are the topics covered with a brief summary of each:

Advanced DSLs in Ruby - Neal Ford

A DSL is a domain specific languages. Basically this is about building fluent (readable) interfaces and improve abstraction by eliminating noise.

So you can type:

recipe = Recipe.new "Spicy Bread"
recipe.add 200.grams.of Flour
recipe.add 1.lb.of Nutmeg

instead of something like:

recipe = Recipe.new("Spicy Bread")
flour = new Ingredient("Flour")
flour.setMeasurement("grams")
flour.setAmount(200)
recipe.add(flour)
nutmeg = new Ingredient("Nutmeg")
nutmeg.setMeasurement("lb")
nutmeg.setAmount(1)
recipe.add(nutmeg)

This works by using a few techniques. First extending the Numeric class to allow the numbers to converted based on the measurement type to a common measurement in grams, and add an of function to build a new Ingredient object

class Numeric
def gram
self
end
alias_method :grams, :gram

def pound
self * 453.59237
end
alias_method :lb, :pound
alias_method :lbs, :pound
alias_method :pounds, :pound

def of ingredient
if ingredient.kind_of String?
ingredient = new Ingredient(ingredient)
end
ingredient.quantity = self
ingredient
end
end

The reason you can create the Ingredient without surrounding the Ingredient name with quotes is by type transmogrification which uses of the const_missing construct:

class Object
def self.const_missing(sym)
eval "Ingredient.new(sym.to_s)"
end
end

Neal added some additional techniques like using bubble method in order to build a nutrition profile for the recipe as you build the recipe.
Overall, the presentation was very interesting to see how you can manipulate Ruby to make ever more readable code. However, in general I don't think it's worth the effort or the confusion for new developers trying to figure out how the code even works.

Working with others: Best Practices for Rails Teams - Luke Francl

Luke discussed some of the issues that can arise in a team environment, specifically for Rails. He had a nice handout which unfortunately isn't available in softcopy form. Here are the bullet points.

  • Migrations - Migrations have a tendency to stop working (what he calls migration decay). So, use the schema.rb as the authoritative source for your database schema. When creating the database on another system run rake db:schema:load instead of running the migrations.
  • Seed Data - Loading seed data in your migrations can break as the models change and fixtures are for test data. Luke recommends using db-populate to populate seed data along with ActiveRecord::Base.create_or_update

    def self.create_or_update(options = {})
    id = options.delete(:id)
    record = find_by_id(id) || new
    record.id = id
    record.attributes = options
    record.save!
    record
    end

  • 3rd party code - vendor everything using gemsonrails
  • Security - HTML escape everything in your views with h. There are several XSS plugins that can be used as well. Mass assignments (ex. LineItem.new(params[:line_item])) can be dangerous, be sure to protect your attributes with:

    • :attr_protected - attributes you can't write to (via mass parameter assignment)

    • :attr_accessible - attributes you can write to



  • Source control - The source code is the life of your project. Be sure to use source control management (SCM). Commit atomic changes, not batches of changes with informative messages.

  • Bug tracking - Use a bug tracker that has some workflow for various states of completion as well as email integration to inform interested parties and SCM integration to link code fixes to bugs.

  • Continuous integration - Tis ties everything together. Verifies that new code doesn't break anything else right away, ensures all libraries and code packages are available, etc.



Rails on AIR - Peter Armstrong


This was mostly about using Flex and using Flex Builder (Eclipse). Flex is way to build Flash applications without knowing Flash and AIR allows your Flash application to run as a stand alone desktop applications. Frankly, I wasn't too interested in this. It seems like a lot of work for not much gain. Yes, Flash is installed in like 99% of browsers, however there are drawbacks to using it in web apps. For example, you site with not be indexable be search engines which can reduce the traffic to your site, and I've been able to accomplish most everything I would need to use Flash for with HTML/CSS/JavaScript.

Keynote - Dan Benjamin


Dan provided some tips for developing good software development and interface design:

  • As Simple As Possible, But No Simpler
  • Focus on Creating a Great User Experience
  • Anticipate User Actions
  • Think Like your Users
  • You're Probably Not as Good of a Designer as you Think
  • Develop for One Scenario, Not for Ten
  • Good Code Does Not Impress Users
  • Don't Release a Beta
  • Apologize (for any problem)
  • Just Ship It
"simplicity is the key to happiness in the modern world"


That's all for day 1. Watch for day two which has these topics:
  • JRuby - Charles Nutter
  • Shining a Light on the Dark Magic of ActiveRecord - Anthony Eden
  • Smarticus University: BDD With RSpec - Bryan Liles
  • Adding Media to Your Rails Application - Dave Naffis and Josh Owens
  • Lessons from the Trenches – Learning from the Rails Bootcamp - Charles Brian Quinn
  • Keynote - Obie Fernandez

Tuesday, January 22, 2008

My ImageFlow with Lightbox packaged sample

Per a request from Clemens to my last post, I've packaged my revised version of ImageFlow and Lightbox2.

In addition, I've made my sample application issue an Ajax request to get the list of images to demonstrate how you can dynamically build the photo album. Here is the code:

// sample application
var MyApp = function() {
var imgTemplate = new Template('#{caption}');

function initializeAlbum(response) {
var imageList;
var imgHTML = '';

try {
imageList = response.responseJSON;
} catch (e) {
console.error(e, "\n", response.responseText);
}

// IE bug : for all other browsers an imageList.each() works great here,
// however IE doesn't like it so we revert to a for loop
for (var i = 0; i < imageList.length; i++) {
var filename = imageList[i];
// IE bug : IE computes an incorrect value for imageList.length resulting
// in undefined filenames in the loop, check for that
if (filename) {
// for simplicity, extract the base filename for the caption.
// Caption could also be defined for each image in the JSON response data.
var caption = filename.split('/').pop().split('.')[0].gsub("%20", " ");
imgHTML += imgTemplate.evaluate({filename : filename, caption : caption});
}
}

$('images').innerHTML = imgHTML;
// IE bug : IE needs a moment to let the innerHTML change register before proceeding
// so we add a small timeout to let it catch its breath
setTimeout(ImageFlow.initialize, 10);
}

return {
loadAlbum : function(albumName) {
$('startButton').hide();
$('photoAlbum').show();
var myAjax = new Ajax.Request('imageList.php?albumName=' + albumName, {
method: 'get',
onSuccess: initializeAlbum
});
}
};

}();

The initial button you see in the sample application, invokes the loadAlbum() function when clicked as shown:

<input type="button" value="Click here to load photo album" onclick="MyApp.loadAlbum('FlickrEyeCandy');"></input>

Download the packaged sample application shown below.



Update January 23, 3pm EST: It was reported that the sample application didn't work in IE. This was due to some deficiencies in IE which I have accounted for now. I have pointed them out in the code with the comments beginning with IE bug

Friday, January 18, 2008

ImageFlow with Lightbox Lite

I like the idea of using ImageFlow with Lightbox2, however, I don't like that it required a tweaked version of ImageFlow, nor that I then had to modify my image list to wrap the Lightbox2 elements around them. Additionally, Lightbox2 adds a lot of functionality that's not being using in this case (all the next/previous image controls, image caching, etc), and it also wants to initialize itself during the window.onload event which I don't want in my Ajax apps.

So, wrote my own Lightbox Lite. Well, technically, I extracted my own Lightbox Lite from the Lightbox2 code with a few modifications.

To begin with, I added this block of HTML to my web page. The Lightbox2 code constructs this dynamically in its initialize function, but I don't see the advantage of that vs just hard coding it into the page to begin with.

<div id="photo" style="display:none">
<div id="lightbox">
<div id="outerImageContainer">
<div id="imageContainer">
<img id="lightboxImage" style="display:none"></img>
<div id="imageloading">
<img src="images/loadingImage.gif">
</div>
</div>
</div>
<div id="imageDataContainer" style="display:none">
<div id="imageData">
<div id="imageDetails">
<span id="imagecaption"></span>
</div>
<div id="imageBottomNav" onclick="LightBoxLite.reset();" style="float:right" >
<img src="images/close-button.png" alt="Close" title="Close">
</div>
</div>
</div>
</div>
</div>

Then I added the relevant CSS rules to my stylesheet:

#photo {
z-index: 51;
position: absolute;
top: 5px;
left: 1px;
width: 100%;
}

#outerImageContainer {
background-color: white;
width: 250px;
height: 250px;
margin: 0 auto;
}

#imageContainer {
padding-top: 10px;
}

#imageloading {
position: absolute;
top: 40%;
left: 47.5%;
}

#imageDataContainer {
font: 10px Verdana, Helvetica, sans-serif;
background-color: white;
margin: 0 auto;
line-height: 1.4em;
overflow: auto;
width: 100%;
padding-bottom: 5px;
}

#imageData {
padding: 0 10px;
color: #666;
}

#imageData #imageDetails {
width: 80%;
float: left;
text-align: left;
}

#imageData #imageCaption {
font-weight: bold;
}

Next I added the Lightbox Lite code to my JavaScript (note, you still need prototype.js and script.aculo.us):

LightBoxLite = function() {

var borderSize = 10;
var resizeDuration = 0.6;

/**
* resizeImageContainer
*
* @param {Number} desired width
* @param {Number} desired height
*/
function resizeImageContainer( imgWidth, imgHeight) {

// get current width and height
var widthCurrent = $('outerImageContainer').getWidth();
var heightCurrent = $('outerImageContainer').getHeight();

// get new width and height
var widthNew = (imgWidth + (borderSize * 2));
var heightNew = (imgHeight + (borderSize * 2));

// scalars based on change from old to new
var xScale = ( widthNew / widthCurrent) * 100;
var yScale = ( heightNew / heightCurrent) * 100;

// calculate size difference between new and old image, and resize if necessary
var wDiff = widthCurrent - widthNew;
var hDiff = heightCurrent - heightNew;

if (!( hDiff === 0)) {
new Effect.Scale('outerImageContainer', yScale, {scaleX: false, duration: resizeDuration, queue: 'front'});
}
if (!( wDiff === 0)) {
new Effect.Scale('outerImageContainer', xScale, {scaleY: false, delay: resizeDuration, duration: resizeDuration});
}

$('imageDataContainer').style.width = widthNew + "px";

showImage();
}

/**
* updateDetails
*
*/
function updateDetails() {
var caption = $('lightboxImage').src.split('/').pop().split('.')[0].gsub("%20", " ");
$('imagecaption').innerHTML = caption;

new Effect.Parallel(
[ new Effect.SlideDown( 'imageDataContainer', { sync: true, duration: resizeDuration, from: 0.0, to: 1.0 }),
new Effect.Appear('imageDataContainer', { sync: true, duration: resizeDuration }) ]
);
}

/**
* showImage
* Display image and begin preloading neighbors.
*/
function showImage() {
$('imageloading').hide();
new Effect.Appear('lightboxImage', { duration: resizeDuration, queue: 'end', afterFinish: updateDetails });
}

function checkForPreloadComplete(imgPreloader) {
if (imgPreloader.complete) {
$('lightboxImage').src = imgPreloader.src;
resizeImageContainer(imgPreloader.width, imgPreloader.height);
} else {
setTimeout(checkForPreloadComplete.bind(this, imgPreloader), 100);
}
}
return {
/**
* displayImage
* display an image in a lightbox
*
* @param {String} URL of image
*/
displayImage : function(imageUrl) {
$('imageloading').show();
$('lightboxImage').hide()
$('imageDataContainer').hide()
$('photo').show();

var imgPreloader = new Image();
imgPreloader.src = imageUrl;

// once image is preloaded, resize image container
setTimeout(checkForPreloadComplete.bind(this, imgPreloader), 100);
},

reset : function() {
$('photo').hide();
$('imageloading').show();
$('lightboxImage').hide()
$('imageDataContainer').hide()
$('lightboxImage').src = "";
$('outerImageContainer').style.width = "250px";
$('outerImageContainer').style.height = "250px";


}
};
}();

The only alteration you have to do to your ImageFlow images is to add a call to the LightBoxLite.displayImage function in the longdesc attribute of your images, which should currently have the address of the original image to display when you click on it in the image flow. So, instead of:

<img src="reflect.php?img=myimage.jpg" longdesc="myimage.jpg" alt="myimage" />

It becomes:

<img src="reflect.php?img=myimage.jpg" longdesc="javascript:LightBoxLite.displayImage('myimage.jpg')" alt="myimage" />


Here is an example of it being used.

Update January 20: I tweaked a couple CSS rules to correctly center the loading indicator for Internet Explorer.

Update January 23: I received a request to package my modifications. See my new blog entry where I have done that in addition to demonstrating how to add Ajax to receive the image list.

Thursday, January 17, 2008

ImageFlow (improved)

Finn Rudolph has created a very nice "cover flow" type of control in JavaScript called ImageFlow (which is an improvement of Michael L. Perry's Cover flow).

It's a very nice package and quite easy to use! However, it currently has a few code shortcomings. My primary issues with it was that it defined everything in a global scope (with common names that easily clash with other functions) and that it assumes the photos are in a static page and thus initializes itself when the web page loads. The later issue was the biggest thing I needed to fix since I wanted to use it in an Ajax application where I'm getting the images as a result of an XMLHttpRequest.

  • I added an "ImageFlow" scoping around the whole package so that all the variables and particularly the functions aren't global to my whole application (and potentially clashing).

  • I added "var" declarations for many of the variables which didn't specify "var" so that they would not become fully global variables, but only global to the ImageFlow scope.

  • I changed the "onload" function to be an "initialize()" function so that I could execute it when my page was ready

  • I made all the variables and functions totally private to ImageFlow with the only publicly visible function being "intialize()"

  • Since the images in the image flow div are now loaded sometime after the page is loaded, I added a check to the initialize function determine when all images are really loaded (otherwise some would be sized wrong).


Now, whenever you're ready for the ImageFlow to be displayed, just call "ImageFlow.initialize()"

If you're interested in these updates, you can download my altered imageflow.js. I have also submitted them to Finn, so hopefully they will be adopted into the official version.

NOTE: The file was updated on January 18, 10:00am EST after running it through JSLint -- which I should have done before posting it originally.

Update January 18: I've added a new article describing the addition of a scaled down version of Lightbox2 without modifying ImageFlow.

Friday, January 11, 2008