I just think Javascript is really icky. Icky like HP printer layout language or raw RTF. I don't know a lot about JavaScript, and I don't really want to. But the world is going bonkers over this gross little language and it's time to put an end to the myth that you must use it to do things in the browser.
I said I don't know a lot about it. Here is the sum of my knowledge and some badly scribbled notes here. I choose to embrace my ignorance of JavaScript and just keep it that way. I am a naiive but happy person. 1
So it's kind of built into me to hate Javascript a bit. JavaScript is also at the center of Polyglot programming which I both admire and despise. I can't deny I like the idea of a programming language in the web browser, but does it have to be Javascript? (and nine other languages thrown in?) Unfortunately, yes, it does. And yet it does not.
Thanks to the promising work of some people out there, as Ruby-lovers and Humanists, we all just may be able to not program in JavaScript as much as possible, and still have the good things in life. This idea grasps at the core of what Ruby is all about. Programmer happiness.
I name a couple of projects: WebRuby by Xuejie Xiao and Opal by Forrest Chang, and I'm sure there are others, but these are the two I've investigated.
The unique quality of these projects, is they run Ruby in the web browser. XueJie's work compiles an entire Ruby VM into a JavaScript library, making it accessible from the browser as a kind of resident machine. Forrest's work is a transliteration engine that recodes a very Ruby-like language into JavaScript - Check out his talk at Miami Ruby Conference 2013. He is not only an engaging speaker with a great slide deck, he has a mission he is passionate about. Coding should be fun, and as a community, Ruby people feel strongly about that.
Both of these projects are highly experimental and I've gotten both to work, with a considerable amount of elbow-grease. XueJie was very helpful in getting my copy of his proof of concept running. I had fewer problems with Forrest's project, but there was a lot of debugging that had to happen with my test program. Forrest has a number of people working on his project, so it was naturally able to handle a more challenging task.
The test program I ran under his project was a copy of something I wrote a few years ago when I was unemployed for almost two years straight. Although that situation remedied itself then unremedied itself again, the program still works. The program just takes some dates of employment and displays a report telling you your total experience both employed and unemployed. I've Perl-tested it, meaning it seems to work fine for me.
Here is the original copy that runs in the console with Ruby MRI:
require 'date';
class WorkHistoryAnalysis
attr_accessor :total, :company, :latest
def initialize()
@total_days = 0
@company = Hash.new
@latest = Date.new
end
def workedFor(who, from, to)
to_d = Date.new(to[0],to[1],to[2])
from_d = Date.new(from[0],from[1],from[2])
latest?(to_d)
days = (to_d - from_d).to_i
if (@company.has_key?(who))
@company[who] += days
else
@company[who] = days
end
@total_days += days
end
def latest?(dat)
if ((dat <=> @latest) > 0)
@latest = dat
else
return false
end
return true
end
def employed
years = days2yrs(@total_days)
return ["Employed for:"],[" #{years} years [#{@total_days} total days]"]
end
def unemployed
days = (Date.today - @latest).to_i
years = days2yrs(days)
return ["Currently unemployed for:"],[" #{years} years [#{days} days]"],["Last worked:"],[" #{@latest.to_s}"]
end
def days2yrs(days)
years = days/365.to_f
return (years * 100).round.to_f / 100
## / just to fix syntax highlighting
end
def list_experiences
str = "Employment breakdown by company:\n"
co = @company.sort_by { |k,v| v }
co.each { |a| str += " #{a[0]} #{days2yrs(a[1])} years [#{a[1]} days]\n" }
return str
end
def print
puts employed
puts unemployed
puts list_experiences
end
end
W = WorkHistoryAnalysis.new
W.workedFor("NWlink",[1996,2,1],[1997,7,1])
W.workedFor("Orrtax",[1997,8,1],[2000,1,1])
W.workedFor("Hostpro",[2000,2,1],[2001,11,1])
W.workedFor("College",[2002,4,1],[2005,12,16])
W.workedFor("Microsoft",[2006,1,1],[2006,5,1])
W.workedFor("Marchex",[2006,6,1],[2008,4,1])
W.workedFor("Microsoft",[2008,10,1],[2009,9,26])
W.workedFor("Microsoft",[2011,9,1],[2012,9,1])
W.print
Opal
in my web browser:
require 'opal'
require 'jquery'
require 'opal-jquery'
require 'date'
class WorkHistoryAnalysis
attr_accessor :total, :company, :latest
def initialize()
@total_days = 0
@company = Hash.new
@last = Date.new(0,0,0)
end
def workedFor(who, from, to)
to_d = Date.new(to[0],to[1],to[2])
from_d = Date.new(from[0],from[1],from[2])
latest?(to_d)
days = (to_d - from_d).to_i
if (@company.has_key?(who))
@company[who] += days
else
@company[who] = days
end
@total_days += days
end
def latest?(dat)
if (dat > @last)
@last = dat
else
return false
end
return true
end
def employed
years = days2yrs(@total_days)
ret = "Employed for:\n"
ret += " #{years} years [#{@total_days} total days]\n"
ret += "\n"
end
def unemployed
days = Date.today - @last
years = days2yrs(days)
ret = "Currently unemployed for:\n"
ret += " #{years} years [#{days} days]\n"
ret += "\n"
ret += "Last worked:\n"
ret += " #{@last.to_s}\n"
ret += "\n"
end
def days2yrs(days)
pre_years = (days/365 * 100).to_i
years = pre_years / 100
return years.to_s
end
def list_experiences
str = "Employment breakdown by company:\n"
co = @company.sort_by { |k,v| v }
co.each { |a| str += " #{a[0]} #{days2yrs(a[1])} years [#{a[1]} days]\n" }
return str
end
def print
return employed + unemployed + list_experiences
end
end
Document.ready? do
body = Element['body']
W = WorkHistoryAnalysis.new
W.workedFor("NWlink",[1996,2,1],[1997,7,1])
W.workedFor("Orrtax",[1997,8,1],[2000,1,1])
W.workedFor("Hostpro",[2000,2,1],[2001,11,1])
W.workedFor("College",[2002,4,1],[2005,12,16])
W.workedFor("Microsoft",[2006,1,1],[2006,5,1])
W.workedFor("Marchex",[2006,6,1],[2008,4,1])
W.workedFor("Microsoft",[2008,10,1],[2009,9,26])
W.workedFor("Microsoft",[2011,9,1],[2012,9,1])
body.append("<pre>#{W.print}<b>")
end
There are a number of minor changes to this script with regard to math and comparison functions. Opal seemed not to like the spaceship operator <=>
very much, so I had to simplify it. The major changes are at the top and bottom of the script and to the naiively-written print function. The top of the script contains require for things that must be in-order, with opal being the first module listed.
The bottom section is wrapped by functions offered by the opal-jquery
library, which essentially makes JQuery accessible to your Ruby script. Note that all JQuery selectors are funneled through Element
objects in Opal.
The goal of this experiment was to get this code to run in my web browser using Opal. First thing, get on IRC into the #opal channel. They can help. It was here I learned that the RVM installation option for Opal was grossly out of date. I discovered myself it had some permissions issues on installation, so I do not recommend installing it. RVM is in a state of flux right now, so this will get fixed at some point. But at the moment, just use the one at Opal's main website: http://opalrb.org/.
Basically, I followed instructions there on how to build a static app that outputs to the JavaScript dev console of Chrome, then when I got that running satisfactorily, I made some modifications to the project to make it run as an Opal JQuery app in the browser proper.
Hopefully, I can give a little auxiliary guidance in this post on how to get to that second stage directly. The full source for this experiement is on GitHub.
First, we will assume you have installed Ruby on your Linux system and created a project directory. We will also assume you have a small console app to test, something that is entirely self-contained for I/O and doesn't need to access any files or take any user input to run.
NOTE: Before any stuff happens, you will need to install node.js
and npm
(the JavaScript package manager) from your Linux distro's package installation tool. These are needed by the cluster-muck of JavaScript frameworks to get the good things in life (and this project) running.
So, once you have the system dependencies installed, the fist (real) step you need to take from your project directory, is to make a subdirectory called app
, and place your script in there. (Use my script if you are not wanting to debug for the next few hours.) Rename it to be application.rb
. You will also need to download a copy of JQuery (just a single file) and place it into the app directory as well, renaming it to jquery.js
. We deal with fixed names here because we are clueless and it makes setup easier by conforming to the instructions.
Next, you will want to create an HTML file in the toplevel project directory for this experiment called simple.html
with the following contents:
<DOCTYPE html>
<html>
<head>
</head>
<body>
<script src="build.js"></script>
</body>
</html>
It references a single Javascript called build.js
which we will compile with Opal momentarily. The deal here is the Ruby you wrote will be cross-compiled into this file, which in turn runs the JavaScript representation of your Ruby code in the web browser. Pretty awesome stuff when you think about it.
Now onto the next step. Create a file in the toplevel called Gemfile
and make sure it has the follwing contents:
source 'https://rubygems.org'
gem "opal", ">= 0.4.3"
gem "opal-sprockets"
gem "opal-jquery"
The source entry is somehwat optional, depending on how your Ruby installation is configured. I needed to insert that line for it to work. The Gemfile is standard equipment on all Ruby projects. It makes it possible to install dependent gems for the project, and we will be doing that in a minute.
Next, create a file called Rakefile
in your toplevel directory with the following contents:
# Rakefile
require 'opal'
require 'opal-sprockets'
require 'opal-jquery'
desc "Build our app to build.js"
task :build do
env = Opal::Environment.new
env.append_path "app"
File.open("build.js", "w+") do |out|
out << env["application"].to_s
end
end
The Rakefile is the Ruby version of a makefile, normally used under Ruby on Rails to do various tasks, but in our case it is being used to cross-compile the Ruby script into JavaScript.
OK, now you have all the startup files in place.
Now for the first point of action. We need to obtain all the gems listed in the Gemfile. That's easy. Not as root, but as yourself, just run > bundle install
. The gems listed on your screen should get downloaded and installed. Bundle is a kind of execution-doer for Ruby projects. 2
After the two or three gems get installed, we will run > rake build
from the toplevel to do our first build. If everything goes well, nothing will show in your console after you run this command. But you will find a new file there called build.js. If you get a dump of errors, scroll back to the beginning of the error output for a clue on why the build didn't work. Otherwise, you are ready to see the result.
To do this, use this clever method of starting a quick web server in your project directory:
> ruby -run -e httpd . -p 3500
This will run a quickie web server on your local machine at port 3500. You can get to it by opening a browser to http://localhost:3500
and it will show a listing of files in that directory for viewing. Just click on simple.html and voila!
I included this projects' Opal Ruby script in this web page. If you see text in the box below, Ruby ran in your web browser, and produced results from the script. It's not just been pasted in to show you what it looks like. No JavaScript had to be written to make this happen:
CLICK ME
Footnotes:
-
I always objected to javaScript because I grew up online using the {COMMO} terminal on BBS's. It was written by
Fred Brucker
(one of my personal programming heroes) and had a large following among people who used screen readers, mostly visually impaired folk. Javascript always seemed like an alienating force to me, becuase is never creates tangible HTML. I don't know for sure, but I'm pretty certain it doesn't do a lot to enhance the user experience of sight-impaired users. Either way I think it's just icky. ↩ -
You can make most Ruby setup stuff run properly with framworks and tools by running
bundle exec <gizmo>
instead of running the gizmo's task directly. But that particular command won't apply here. Just a good thing to know. ↩