This post originated from an RSS feed registered with Ruby Buzz
by Chris Nelson.
Original Post: Write your javascript in ruby with rubyjs
Feed Title: MysteryCoder
Feed URL: http://mysterycoder.blogspot.com/feeds/posts/default
Feed Description: Ramblings of secret Ruby coder in Cincinnati, OH
I was inspired by Nathaniel Talbott's awesome railsconf talk to do some hacking. I had a lot of fun, and I think I came up with something worth sharing, so here it is. My willing victim was rubyjs, Michael Neumann's excellent little project I mentioned in an earlier post. If you want to play along at home, the first thing you'll need to do is fetch my code like so:
git clone git://github.com/superchris/rubyjs.git
There are some things in my repo that haven't made it into the gem version of rubyjs yet. My hack was a little "port" of the hangman example from Tapestry. It's there in examples/hangman. You can try it here. Or if you're so inclined, you can build it your dang self by running:
rake "examples/hangman/hangman.js"
This command uses a rake rule to run the rubyjs compiler which compiles hangman.rb to hangman.js.
So let's see some code how about, hmm?
class DOMElement def initialize(element) @dom_element = element end
def observe(event, &block) element = @dom_element ` if (#<element>.addEventListener) { #<element>.addEventListener(#<event>, #<block>, false); } else { #<element>.attachEvent("on" + #<event>, #<block>); } ` nil end
def [](attribute) element = @dom_element `return #<element>[#<attribute>]` end
def []=(attr, value) element = @dom_element `#<element>[#<attr>] = #<value>;` nil end
def self.find_js_element(element) `return document.getElementById(#<element>);` end
def self.find(element) dom_element = self.find_js_element(element) DOMElement.new(dom_element) end
# # Gets an HTML representation (as String) of an element's children. # # elem:: the element whose HTML is to be retrieved # return:: the HTML representation of the element's children # def inner_html elem = @dom_element ` var ret = #<elem>.innerHTML; return (ret == null) ? #<nil> : ret;` end
# # Sets the HTML contained within an element. # # elem:: the element whose inner HTML is to be set # html:: the new html # def inner_html=(html) elem = @dom_element ` #<elem>.innerHTML = #<html>; return #<nil>;` end
end
The first class you see here is DOMElement. This gives some basic dom manipulation abilities in a ruby friendly way. I got this working by looking at the work Michael had done on porting GWT to ruby and extracting the bit I wanted and "rubifying" it a little. Basically, you can find elements by id, observe events with ruby blocks, and get/set attributes and inner html. Pretty simple, but has what I need. I should probably extract this into a separate file in rubyjs in case other people want it. You can also see lots of examples of how rubyjs and javascript talk to each other here.
Then it's on to the hangman code. First it's worth looking at the html so you can see what dom elements the code refers to:
def display_word letters.collect do |letter| @guessed_letters[letter] ? letter : "_" end.join end
def guess(letter) if letters.include?(letter) @guessed_letters[letter] = true @letters_div.inner_html = display_word puts "You win!" if won? else @misses += 1 @scaffold_div.inner_html = "<img src='scaffold-#{@misses}.png' />" if lost? puts "You lost!" @guess_button["disabled"] = true end end @guess_input["value"] = "" end
def lost? @misses >= 6 end
def won? @guessed_letters.values.each do |guessed| return false unless guessed end return true end
def self.main hangman = Hangman.new rescue StandardError => ex puts ex end end
First you see the initialize method. Here is where we lookup our DOM elements, setup a Hash of which letters are guessed yet, and bind a block to the click event of our guess_button. Next comes the display_word method, which displays each letter or a blank if it's not been guessed.
The meat of the matter is in guess_letter, which is pretty simple. If the letter guessed is in the word we mark it, redisplay the word with the guessed letter and check if the user has won. If not, we update our miss count, display the right image, and check to see if the user lost. Won? and lost? are both trivial and not worth talking about.
Well, I had a lot of fun hacking on this. I think rubyjs has a good chance to be really useful as well as fun. It needs some love, certainly, but one of the things I did was start of woefully inadequate port of miniunit I'm calling microunit. It's in rubyjs/lib. This should make it easier and, I think, funner, to flesh out the core library for rubyjs.
For next steps I may expand on this example some. I'm thinking it would be a blast to tie to a Rails backend that gives me random words (right now it is always the same word :( ) or stores scores or some such foolishness. I could use this as an excuse to build an ActiveResource client for rubyjs and maybe a rubyjs on rails plugin.
If anyone cares about this, drop me a comment and let me know. Or if you think it's utterly stupid, tell me why. I promise to read all your comments and do whatever I feel like doing anyways :)