<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Erik A. Hanson &#187; Web development</title>
	<atom:link href="http://www.eahanson.com/category/web-development/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.eahanson.com</link>
	<description>My weblog</description>
	<lastBuildDate>Mon, 24 May 2010 04:49:46 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>A Ruby Client for Angular</title>
		<link>http://www.eahanson.com/2009/11/19/a-ruby-client-for-angular/</link>
		<comments>http://www.eahanson.com/2009/11/19/a-ruby-client-for-angular/#comments</comments>
		<pubDate>Fri, 20 Nov 2009 04:29:08 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[My Software]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=120</guid>
		<description><![CDATA[Here&#8217;s a little Ruby client for storing data in Angular, a system for creating simple database-backed web apps extremely easily. #!/usr/bin/ruby require 'rubygems' require 'httpclient' require 'json' class Angular def initialize(username, password, library) @http = HTTPClient.new @library = library post("/login", :email => username, :password => password) end def libraries get("/data") end def store(database, document, data) [...]]]></description>
			<content:encoded><![CDATA[<p>Here&#8217;s a little Ruby client for storing data in <a href="http://www.getangular.com">Angular</a>, a system for creating simple database-backed web apps extremely easily. </p>
<pre>
#!/usr/bin/ruby

require 'rubygems'
require 'httpclient'
require 'json'

class Angular
  def initialize(username, password, library)
    @http = HTTPClient.new
    @library = library
    post("/login", :email => username, :password => password)
  end

  def libraries
    get("/data")
  end

  def store(database, document, data)
    post("/data/#{database}/#{document}", data.to_json)
  end

  private

  def post(path, params)
    request(:post, path, params)
  end

  def get(path, params=nil)
    request(:get, path, params)
  end

  def request(method, path, params)
    uri = (path.index("http") == 0) ? path : ("http://#{@library}.getangular.com" + path)
    response = @http.send(method, uri, params)
    if response.code == 302
      get(response.header["Location"][0])
    elsif response.code == 200
      parsed = parse(response)
      status_code = parsed['$status_code']
      raise "#{uri.to_s} responded with #{status_code}" if status_code &#038;&#038; status_code != 200
      parsed
    else
      raise "#{uri.to_s} responded with #{response.code}"
    end
  end

  def parse(response)
    begin
      JSON.parse(response.body.content)
    rescue
      {}
    end
  end
end
</pre>
<p>Also <a href="http://github.com/eahanson/angular-ruby">available on GitHub</a> for your forking pleasure.</p>
<p>Maybe in the future I&#8217;ll write up a blog post about how I&#8217;m using Angular.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2009/11/19/a-ruby-client-for-angular/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Running Jasmine Tests Through Jaxer</title>
		<link>http://www.eahanson.com/2009/06/04/running-jasmine-tests-through-jaxer/</link>
		<comments>http://www.eahanson.com/2009/06/04/running-jasmine-tests-through-jaxer/#comments</comments>
		<pubDate>Fri, 05 Jun 2009 00:51:39 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Jasmine]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Jaxer]]></category>
		<category><![CDATA[Unit Testing]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=98</guid>
		<description><![CDATA[Jasmine? Jaxer? Jasmine is a new Javascript testing framework from my pals at Pivotal Labs. Jaxer is an Apache module that wraps Firefox&#8217;s Javascript and rendering engine, providing a server-side Javascript environment that&#8217;s fast and compatible with any JS that works in Firefox (including all kinds of DOM manipulation and libraries like Prototype, jQuery, etc.). [...]]]></description>
			<content:encoded><![CDATA[<h3>Jasmine? Jaxer?</h3>
<p><a href="http://github.com/pivotal/jasmine/tree/master">Jasmine</a> is a new Javascript testing framework from my pals at <a href="http://pivotallabs.com/">Pivotal Labs</a>. </p>
<p><a href="http://www.aptana.com/jaxer">Jaxer</a> is an Apache module that wraps Firefox&#8217;s Javascript and rendering engine, providing a server-side Javascript environment that&#8217;s fast and compatible with any JS that works in Firefox (including all kinds of DOM manipulation and libraries like Prototype, jQuery, etc.).</p>
<h3>Running Jasmine Tests Through Jaxer</h3>
<p>Part of an app I&#8217;m writing uses Jaxer and I wanted to write my tests in Jasmine, so I wrote a test runner and reporter to bridge the two, and then I wrote a rake task to run the tests.</p>
<p>So now I can run &#8220;rake spec&#8221; and my JS tests will run, in Firefox, but without having to actually have Firefox open up (or be installed).</p>
<p>So:</p>
<pre>
rake spec
</pre>
<p>returns:</p>
<pre>
.    foo should do something.
.    foo should do something else.
.    foo should do yet another thing.

3 of 3 specs passed
</pre>
<p>And the whole thing takes <b>0.37 seconds</b>. It should be noted that those are trivial tests, but running a JS test suite with only 0.37 seconds of overhead is pretty neat.</p>
<p>My rake task looks like:</p>
<pre>
task :spec do
  results = `curl -s http://127.0.0.1:8081/jaxer-service/jasmine-jaxer`
  print results
  fail "spec failures" if results.match(/FAILURES!/)
end
</pre>
<h3>Download</h3>
<p>The JS code that hooks Jasmine to Jaxer is here: <a href="http://www.eahanson.com/code/jasmine-jaxer.js">jasmine-jaxer.js</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2009/06/04/running-jasmine-tests-through-jaxer/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>A Bookmarklet To Reload A Page&#8217;s CSS</title>
		<link>http://www.eahanson.com/2009/02/28/a-bookmarklet-to-reload-a-pages-css/</link>
		<comments>http://www.eahanson.com/2009/02/28/a-bookmarklet-to-reload-a-pages-css/#comments</comments>
		<pubDate>Sun, 01 Mar 2009 02:46:05 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=62</guid>
		<description><![CDATA[Sometimes when I&#8217;m developing, I want to change some stylesheets and reload them without reloading the whole page. Often, I use Firebug or the Safari Web Inspector to play with individual attributes, but sometimes I have to edit the CSS files. So I wrote a bookmarklet to reload the files. Here it is: reload CSS [...]]]></description>
			<content:encoded><![CDATA[<p>Sometimes when I&#8217;m developing, I want to change some stylesheets and reload them without reloading the whole page. Often, I use Firebug or the Safari Web Inspector to play with individual attributes, but sometimes I have to edit the CSS files.</p>
<p>So I wrote a bookmarklet to reload the files. Here it is:</p>
<p><a href="javascript:(function()%20%7Bvar%20links%20=%20document.getElementsByTagName(%22link%22);for%20(var%20i%20=%200;%20i%20%3C%20links.length;%20i++)%20%7Bif%20(links%5Bi%5D.rel%20===%20%22stylesheet%22)%20%7Bif%20(links%5Bi%5D.href.indexOf(%22?%22)%20===%20-1)%20%7Blinks%5Bi%5D.href%20+=%20%22?%22;%7Dlinks%5Bi%5D.href%20+=%20%22x%22;%7D%7D%7D)()">reload CSS</a></p>
<p>(Just drag that into your bookmarks bar. Tested in Safari and Firefox.)</p>
<p>It works by appending the letter &#8220;x&#8221; after a &#8220;?&#8221; at the end of each stylesheet reference. That causes the browser to reload the stylesheet. Here&#8217;s the code:</p>
<pre>
var links = document.getElementsByTagName("link");
for (var i = 0; i < links.length; i++) {
  if (links[i].rel === "stylesheet") {
    if (links[i].href.indexOf("?") === -1) {
      links[i].href += "?";
    }
    links[i].href += "x";
  }
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2009/02/28/a-bookmarklet-to-reload-a-pages-css/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Introducing JS Dev Tools</title>
		<link>http://www.eahanson.com/2009/02/28/introducing-js-dev-tools/</link>
		<comments>http://www.eahanson.com/2009/02/28/introducing-js-dev-tools/#comments</comments>
		<pubDate>Sun, 01 Mar 2009 02:05:35 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[My Software]]></category>
		<category><![CDATA[Web development]]></category>
		<category><![CDATA[jsdevtools.com]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=57</guid>
		<description><![CDATA[I created a site called JS Dev Tools (jsdevtools.com) to keep track of useful Javascript development tools and libraries. Check out the site, subscribe to the feed, and suggest your favorite tools and libraries.]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.jsdevtools.com"><img src="http://www.eahanson.com/weblog/wp-content/uploads/2009/02/jsdevtools.png" alt="jsdevtools" title="jsdevtools" width="400" height="193" class="aligncenter size-full wp-image-59" /></a></p>
<p>I created a site called JS Dev Tools (<a href="http://www.jsdevtools.com">jsdevtools.com</a>) to keep track of useful Javascript development tools and libraries.</p>
<p>Check out <a href="http://www.jsdevtools.com">the site</a>, subscribe to <a href="http://www.jsdevtools.com/rss.xml">the feed</a>, and <a href="mailto:submissions@jsdevtools.com">suggest your favorite tools and libraries</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2009/02/28/introducing-js-dev-tools/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>A Rake Task to Concatenate and Compress Javascript Files</title>
		<link>http://www.eahanson.com/2008/03/02/a-rake-task-to-concatenate-and-compress-javascript-files/</link>
		<comments>http://www.eahanson.com/2008/03/02/a-rake-task-to-concatenate-and-compress-javascript-files/#comments</comments>
		<pubDate>Mon, 03 Mar 2008 06:15:52 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Ruby on Rails]]></category>
		<category><![CDATA[Web development]]></category>
		<category><![CDATA[wshlst.com]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/2008/03/02/a-rake-task-to-concatenate-and-compress-javascript-files/</guid>
		<description><![CDATA[A while ago, I wrote about a Ruby script to concatenate and compress Javascript files for my wshlst application. I&#8217;ve changed things around a bit since that article, so I thought it was time for an update. Background wshlst uses a lot of Javascript. When developing, I want to have all of my JS files [...]]]></description>
			<content:encoded><![CDATA[<p>A while ago, I wrote about <a href="http://www.eahanson.com/2007/10/16/a-ruby-script-to-concatenate-and-compress-javascript-files/">a Ruby script to concatenate and compress Javascript files</a> for my <a href="http://www.eahanson.com/category/wshlstcom/">wshlst</a> application. I&#8217;ve changed things around a bit since that article, so I thought it was time for an update.</p>
<h3>Background</h3>
<p><a href="http://www.eahanson.com/category/wshlstcom/">wshlst</a> uses <a href="http://www.eahanson.com/2007/10/25/wshlstcom-a-webapp-with-a-pure-javascript-ui/">a lot of Javascript</a>. When developing, I want to have all of my JS files listed in my <code>index.html</code> page. But in production mode, I want a single JS file. So I wrote a script to read in my <code>index.html</code> file, find all the references to my Javascript files, and write out a new HTML file that contains a reference to a single JS file that was created by concatenating and compressing all the JS files.</p>
<p>As an example, have a look at what my <code>index.html</code> page looks like in <a href="http://eahanson.com/code/wshlst-index.txt">development mode</a> and in <a href="http://eahanson.com/code/wshlst-index-mini.txt">production mode</a>.</p>
<h3>Ruby Script &rarr; Rake</h3>
<p>When a server move prompted me to upgrade to Subversion and Capistrano, I decided to turn my Ruby script into a Rake task that I could trigger from my Capistrano deploy: <a href="http://www.eahanson.com/code/minify.rake">minify.rake</a></p>
<h3>Capistrano</h3>
<p>I added a line to my Capistrano <code>deploy.rb</code> file to run <code>rake minify</code> on the server after updating from Subversion:</p>
<pre>run "cd #{release_path} &#038;&#038; rake minify RAILS_ENV=#{rails_env}"</pre>
<h3>Apache</h3>
<p>I also added a few lines to my <code>httpd.conf</code> file to support this. First, since the Rake task combines all the JS files into a single JS file but Capistrano deploys the entire app, I wanted to restrict access to everything in the <code>javascripts</code> directory:</p>
<pre># Don't allow access to the javascripts directory (all JS should be in minified js file)
RewriteRule ^/javascripts/ - [F]</pre>
<p>Then, because the Rake task reads <code>index.html</code> and outputs a file called <code>index-mini.html</code>, I wanted to send all requests for <code>/</code> and <code>/index.html</code> to <code>/index-mini.html</code>:</p>
<pre>
# Rewrite index.html to index-mini.html
RewriteRule ^/index.html$ /index-mini.html [QSA]

# Rewrite index to check for static
RewriteRule ^/$ /index-mini.html [QSA]
</pre>
<h3>Why Not Use AssetPackager?</h3>
<p>For a normal Rails application, <a href="http://synthesis.sbecker.net/pages/asset_packager">AssetPackager</a> is probably the right solution to this problem. However, <a href="http://www.eahanson.com/category/wshlstcom/">wshlst</a> doesn&#8217;t use Rails to generate the UI (<a href="http://www.eahanson.com/2007/10/25/wshlstcom-a-webapp-with-a-pure-javascript-ui/">it&#8217;s all done in Javascript</a>), so AssetPackager wouldn&#8217;t work.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2008/03/02/a-rake-task-to-concatenate-and-compress-javascript-files/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Firing mouse events in tests</title>
		<link>http://www.eahanson.com/2008/01/01/firing-mouse-events-in-tests/</link>
		<comments>http://www.eahanson.com/2008/01/01/firing-mouse-events-in-tests/#comments</comments>
		<pubDate>Wed, 02 Jan 2008 04:17:48 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[JsUnit]]></category>
		<category><![CDATA[Unit Testing]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/2008/01/01/firing-mouse-events-in-tests/</guid>
		<description><![CDATA[The Bad News Sending mouse events such as click and mouseover in JsUnit tests can be really hard. More Bad News Prototype doesn&#8217;t make it any easier. Sam Stephenson says: We would very much like to support it in the future. It&#8217;s fairly complicated to implement native event firing across all supported browsers, so in [...]]]></description>
			<content:encoded><![CDATA[<h4>The Bad News</h4>
<p>Sending mouse events such as <code>click</code> and <code>mouseover</code> in <a href="http://jsunit.net">JsUnit</a> tests can be really hard.</p>
<h4>More Bad News</h4>
<p><a href="http://prototypejs.org">Prototype</a> doesn&#8217;t make it any easier. Sam Stephenson <a href="http://groups.google.com/group/prototype-core/browse_thread/thread/9fec287978138250">says</a>:</p>
<blockquote><p>
We would very much like to support it in the future.  It&#8217;s fairly complicated to implement native event firing across all supported browsers, so in 1.6.0, fire works with custom events only.
</p></blockquote>
<h4>YUI To The Rescue</h4>
<p><a href="http://developer.yahoo.com/yui/yuitest/#useractions">YAHOO.util.UserActions</a> can simulate some user actions. Unfortunately, calls to YUI can look a bit clunky in a Prototype-heavy codebase:</p>
<pre>
var element = new Element("div").insert("Hi");
var offset = element.cumulativeOffset();
YAHOO.util.UserAction.click(element, { shiftKey: true });
</pre>
<h4>YUI + Prototype FTW</h4>
<p>A little mixin magic:</p>
<pre>
Element.addMethods({
  simulateClick: YAHOO.util.UserAction.click.bind(YAHOO.util.UserAction),
  simulateDblClick: YAHOO.util.UserAction.dblclick.bind(YAHOO.util.UserAction),
  simulateMousedown: YAHOO.util.UserAction.mousedown.bind(YAHOO.util.UserAction),
  simulateMouseup: YAHOO.util.UserAction.mouseup.bind(YAHOO.util.UserAction),
  simulateMouseover: YAHOO.util.UserAction.mouseover.bind(YAHOO.util.UserAction),
  simulateMouseout: YAHOO.util.UserAction.mouseout.bind(YAHOO.util.UserAction),
  simulateMousemove: YAHOO.util.UserAction.mousemove.bind(YAHOO.util.UserAction)
});
</pre>
<p>and now our test code looks nicer:</p>
<pre>
var element = new Element("div").insert("Hi");
var offset = element.cumulativeOffset();
myElement.simulateClick({ shiftKey: true });
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2008/01/01/firing-mouse-events-in-tests/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>My time zone solution: RelativeDate.js and ServerTime.js</title>
		<link>http://www.eahanson.com/2007/10/31/my-time-zone-solution-relativedatejs-and-servertimejs/</link>
		<comments>http://www.eahanson.com/2007/10/31/my-time-zone-solution-relativedatejs-and-servertimejs/#comments</comments>
		<pubDate>Wed, 31 Oct 2007 21:29:05 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[My Software]]></category>
		<category><![CDATA[Ruby on Rails]]></category>
		<category><![CDATA[Web development]]></category>
		<category><![CDATA[wshlst.com]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/2007/10/31/my-time-zone-solution-relativedatejs-and-servertimejs/</guid>
		<description><![CDATA[For my wshlst.com project, I needed to show when each item or comment was created or edited. My initial implementation was to just show the time and date of the change, but people who aren&#8217;t in the same time zone as my server didn&#8217;t like the fact that it showed a different time zone. I [...]]]></description>
			<content:encoded><![CDATA[<p>For my <a href="http://www.eahanson.com/category/wshlstcom/">wshlst.com</a> project, I needed to show when each item or comment was created or edited. My initial implementation was to just show the time and date of the change, but people who aren&#8217;t in the same time zone as my server didn&#8217;t like the fact that it showed a different time zone.</p>
<p>I contemplated having a per-user time zone setting, but that seemed complicated. So I decided to just show how old the item is. For that, I needed to write two things. I&#8217;m posting them here in case anyone else finds them useful.</p>
<h3>RelativeDate.js</h3>
<p>First, I needed a simple relative date formatter, so I wrote a simple one. Given two dates, it returns a string like </p>
<pre>
4 days ago
</pre>
<p>or </p>
<pre>
26 hours ago
</pre>
<p>Here&#8217;s the code and the <a href="http://jsunit.net">JsUnit</a> test:</p>
<p><a href="/code/RelativeDate/RelativeDate.js">RelativeDate.js</a><br />
<a href="/code/RelativeDate/RelativeDateTest.html.txt">RelativeDateTest.html</a></p>
<h3>ServerTime.js</h3>
<p>The timestamps of the items I&#8217;m displaying are stored in the database, so they are based on the clock of my database server. The relative dates are calculated in Javascript, so they are based on the clock of the user, which might be hours or just minutes off from the database server time. </p>
<p>The first thing my app does when it loads is checks to see if the user is logged in, so I decided to piggyback the current server time along with the response. Since the client and server talk using JSON, it&#8217;s simple to add another property to the response:</p>
<pre>
def current
  ...
  render_json({
    :success => true,
    :user => user,
    :servertime => dbtime()
  }.to_json)
end

...

def dbtime
  ActiveRecord::Base
    .connection
    .select_all("select unix_timestamp(now()) as now")[0]["now"]
    .to_i
end
</pre>
<p>I wrote a ServerTime class on the client side and I initialize it when the app receives the current user response from the server:</p>
<pre>
this.serverTime = new ServerTime(json.servertime);
</pre>
<p>Here&#8217;s the code (it&#8217;s very simple):</p>
<p><a href="/code/ServerTime.js">ServerTime.js</a></p>
<h3>Putting It Together&#8230;</h3>
<p>And now to display the relative time, my app only has to do this:</p>
<pre>
var updated = new Date(item.updated_at * 1000);
element.insert(new RelativeDate(updated, app.serverTime.get()).toString());
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/10/31/my-time-zone-solution-relativedatejs-and-servertimejs/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>wshlst.com: A Webapp With A Pure-Javascript UI</title>
		<link>http://www.eahanson.com/2007/10/25/wshlstcom-a-webapp-with-a-pure-javascript-ui/</link>
		<comments>http://www.eahanson.com/2007/10/25/wshlstcom-a-webapp-with-a-pure-javascript-ui/#comments</comments>
		<pubDate>Thu, 25 Oct 2007 22:51:08 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[My Software]]></category>
		<category><![CDATA[Web development]]></category>
		<category><![CDATA[wshlst.com]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/2007/10/25/wshlstcom-a-webapp-with-a-pure-javascript-ui/</guid>
		<description><![CDATA[I created a little wish list website called wshlst.com for my family to use. (Read more about it here.) I worked on it a little bit before Christmas last year, and a little bit again this year. It&#8217;s been a lot of fun to write, and one reason is that the entire UI is written [...]]]></description>
			<content:encoded><![CDATA[<p>I created a little wish list website called <a href="http://www.wshlst.com/">wshlst.com</a> for my family to use. (<a href="http://www.eahanson.com/2007/10/24/wshlstcom/">Read more about it here</a>.) I worked on it a little bit before Christmas last year, and a little bit again this year.</p>
<p>It&#8217;s been a lot of fun to write, and one reason is that the entire UI is written in Javascript. No HTML, no CSS. There&#8217;s no HTML generation on the server-side either; it&#8217;s all client-side Javascript. I&#8217;d really like to write more apps like this.</p>
<h3>The Server</h3>
<p>There is of course a server-side component. I decided to write the server in Rails because I had just finished working on a Rails project at <a href="http://www.pivotallabs.com/">Pivotal Labs</a> and had Rails on the brain. Plus, my web host, <a href="http://www.dreamhost.com/r.cgi?29771">DreamHost</a>, supports Rails and doesn&#8217;t support Java, which is the language I&#8217;ve written all my other web apps in.</p>
<p>DreamHost&#8217;s support of Rails is a bit limited though: they kill any long-running processes, which means that a response from your Rails app could take 10 seconds if no other requests have been made lately. It&#8217;s good enough for a little project like Wshlst with only a few users and for initial development, but not much beyond that. (If I were to do it over again, I&#8217;d probably just use Ruby CGI, or maybe even PHP. Or use a VPS from <a href="http://www.rimuhosting.com/">Rimu</a>, but that costs money.)</p>
<p>Because all of the UI is in Javascript, the Rails side of things doesn&#8217;t have to generate any HTML. Instead, it takes standard Rails requests and returns <a href="http://json.org/">JSON</a> using <a href="http://json.rubyforge.org/">JSON for Ruby</a>. For example, the client sends the following AJAX request:</p>
<pre>
GET http://wshlst.com/user/list
</pre>
<p>and the server uses ActiveRecord magic to find all the users that are &#8220;friends&#8221; with the current user and uses JSON for Ruby to convert that list to JSON, which it returns back to the client like this:</p>
<pre>
[{name: "Andy Acorn", id: 12, email: "andy@example.com"},
{name: "Betty Beets", id: 13, email: "betty@example.com"},
{name: "Chad Carrot", id: 14, email: "chad@example.com"},
{name: "Donna Donut", id: 15, email: "donna@example.com"}]
</pre>
<p>which Prototype automatically converts to Javascript objects.</p>
<p>The server is of course also responsible for sending static files, but those are all handled by DreamHost&#8217;s Apache servers.</p>
<h3>The Client</h3>
<p>Here&#8217;s where it gets interesting. The app consists of an HTML file that does little more than include the Javascript files. (In development mode, there are multiple Javascript files, but when I deploy to production, there&#8217;s just one file. <a href="http://www.eahanson.com/2007/10/16/a-ruby-script-to-concatenate-and-compress-javascript-files/">Read this blog post on concatenating and compressing Javascript files with Ruby</a>.)</p>
<p>The app relies heavily on <a href="http://prototypejs.org/">Prototype</a>, and also uses <a href="http://www.berniecode.com/writing/animator.html">Animator.js</a> for a few visual effects. Everything else I wrote from scratch. I like to write a lot of code from scratch when I&#8217;m working on a personal project so I can get a good understanding of what&#8217;s really going on and be able to better evaluate pre-written libraries when I&#8217;m programming for money.</p>
<p>The app&#8217;s main class is called Application and acts as a controller. There are many view classes which are combined to form the app&#8217;s UI. For example, the &#8220;Change Password&#8221; box is implemented by a ChangePasswordForm class and consists of a ModalBox with a FormPanel inside. The FormPanel contains instances of Buttons and Fields. Writing a Field class of course takes more time than putting <tt>&lt;input type="text"&gt;</tt> in some template file somewhere, but it allows for easy reuse and the ability to add a feature to all fields in just one place. (I hope to post some of the code from Wshlst on this blog in the future.)</p>
<p>Responding to user actions is just a matter of one Javascript method directly calling another method. No parsing of parameters, no layers of actions and filters, no guessing client state on the server side. It&#8217;s very liberating. </p>
<h3>Lessons Learned</h3>
<p><b>Test early and often in lots of browsers.</b> This is obviously important when doing any web development, but when it&#8217;s 100% Javascript, there&#8217;s even more chance of incompatibilities. My <a href="http://jsunit.net/">JsUnit</a> tests caught some issues (<tt>Date.now</tt> turns out to be Firefox-only), but most issues I had were visual.</p>
<p><b>MVC is good.</b> Again, I already knew this, but I didn&#8217;t do nearly a good enough job with it in this app, probably because it was the first time I wrote an entire UI in Javascript. Next time, I&#8217;ll have multiple controllers and keep app logic out of the views as much as possible.</p>
<p><b>Events are probably the way to go.</b> Wshlst suffers from some tight coupling of objects because they directly notify each other of actions. Next time, I&#8217;ll try having all my objects send custom events to and receive custom events from some global event source (perhaps <tt>document.body</tt>), like I did back in the days when I wrote Mac software.</p>
<p><b>UI is hard.</b> I&#8217;m glad I did all the UI myself, but next time I&#8217;d like to build on the work of others. I&#8217;m still looking for a good Javascript widget library. Some of the widget libraries I&#8217;ve seen are trying to make web apps look application-y, and I&#8217;d prefer my web app to look web-y. Also, some libraries don&#8217;t have great support for calling everything from Javascript. I think I&#8217;ll look into <a href="http://dojotoolkit.org/">Dojo</a> again now that version 1.0 has been released, and more importantly, now that there&#8217;s more documentation.</p>
<h3>Try It Out</h3>
<p>There&#8217;s no way yet to sign up for Wshlst (if there&#8217;s enough interest I&#8217;ll do it), but the front page of the site lists some demo users you can use to play around: <a href="http://www.wshlst.com/">www.wshlst.com</a>.</p>
<p><i><a href="http://www.eahanson.com/category/wshlstcom/">Read more articles about wshlst.com</a></i></p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/10/25/wshlstcom-a-webapp-with-a-pure-javascript-ui/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>A Ruby Script to Concatenate and Compress Javascript Files</title>
		<link>http://www.eahanson.com/2007/10/16/a-ruby-script-to-concatenate-and-compress-javascript-files/</link>
		<comments>http://www.eahanson.com/2007/10/16/a-ruby-script-to-concatenate-and-compress-javascript-files/#comments</comments>
		<pubDate>Wed, 17 Oct 2007 06:07:38 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/2007/10/16/a-ruby-script-to-concatenate-and-compress-javascript-files/</guid>
		<description><![CDATA[I&#8217;ve got an HTML page that includes a bunch of Javascript files, which makes development easy but which hurts performance in production. The standard solution is to concatenate all the Javascript files into one big file and then compress it. I wanted it to be automated of course, and I wanted to do it at [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve got an HTML page that includes a bunch of Javascript files, which makes development easy but which <a href="http://yuiblog.com/blog/2006/11/28/performance-research-part-1/">hurts performance</a> in production.</p>
<p>The standard solution is to concatenate all the Javascript files into one big file and then compress it. I wanted it to be automated of course, and I wanted to do it at deploy-time, not at run-time. </p>
<p>So I wrote a little script that figures out what Javascript files are being included in the HTML page, compresses them with the <a href="http://developer.yahoo.com/yui/compressor/">YUI Compressor</a>, concatenates them into a file named after the newly-compressed and -concatenated Javascript&#8217;s MD5 hash, and creates a copy of the original HTML page that references the new Javascript file instead of the old ones.</p>
<p>It&#8217;s pretty specific to my app&#8217;s needs, but with some minor tweaking, it should be useable on other projects. Check it out: <b><a href="http://www.eahanson.com/code/minify.rb">minify.rb</a></b></p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/10/16/a-ruby-script-to-concatenate-and-compress-javascript-files/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Element.stylize via Prototype.js</title>
		<link>http://www.eahanson.com/2007/05/25/elementstylize-via-prototypejs/</link>
		<comments>http://www.eahanson.com/2007/05/25/elementstylize-via-prototypejs/#comments</comments>
		<pubDate>Fri, 25 May 2007 20:13:18 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/weblog/?p=15</guid>
		<description><![CDATA[I often want to add a bunch of styles to an element, so I wrote a simple stylize method.]]></description>
			<content:encoded><![CDATA[<p>I often want to add a bunch of styles to an element, so I wrote a <tt>stylize</tt> method so I can do something like this:</p>
<pre>
$("foo").stylize({
  color: red,
  background-color: green,
  text-decoration: underline
});</pre>
<p>Implementing <tt>stylize</tt> is easy with <a href="http://prototypejs.org/">Prototype</a>&#8216;s <a href="http://prototypejs.org/api/element/addMethods">Element.addMethods()</a> method:</p>
<pre>
var ElementExtensions = {
  stylize: function(element, styles) {
    for (var s in styles) {
      element.style[s] = styles[s];
    }
    return element;
  }
}
Element.addMethods(ElementExtensions);</pre>
<p><b>Update:</b> <a href="http://prototypejs.org/2007/8/15/prototype-1-6-0-release-candidate">Prototype 1.6</a> added a similar <tt>setStyle</tt> method which works similarly but also allows CSS string-type style declaration (e.g., <tt>myElement.setStyle("color: blue; border: 3px")</tt>). Still, the above is a nice example of <tt>Element.addMethods()</tt>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/05/25/elementstylize-via-prototypejs/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Installing termios gem on Mac OS X</title>
		<link>http://www.eahanson.com/2007/02/03/installing-termios-gem-on-mac-os-x/</link>
		<comments>http://www.eahanson.com/2007/02/03/installing-termios-gem-on-mac-os-x/#comments</comments>
		<pubDate>Sun, 04 Feb 2007 03:13:54 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Mac OS X]]></category>
		<category><![CDATA[Ruby on Rails]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/weblog/?p=13</guid>
		<description><![CDATA[When installing Capistrano, you should install the termios gem so that your password isn't echoed when you type it. Easier said than done.]]></description>
			<content:encoded><![CDATA[<p>When installing <a href="http://manuals.rubyonrails.com/read/chapter/97">Capistrano</a>, you should install the <strong>termios</strong> gem so that your password isn&#8217;t echoed when you type it.</p>
<h4>Easier said than done</h4>
<p>Here&#8217;s the error I got:</p>
<div class="code">$<span style="color: darkgreen">sudo gem install termios</span><br />
Attempting local installation of &#8216;termios&#8217;<br />
Local gem file not found: termios*.gem<br />
Attempting remote installation of &#8216;termios&#8217;<br />
Updating Gem source index for: http://gems.rubyforge.org<br />
Building native extensions.  This could take a while&#8230;<br />
can&#8217;t find header files for ruby.<br />
ERROR:  While executing gem &#8230; (RuntimeError)<br />
ERROR: Failed to build gem native extension.<br />
Gem files will remain installed in /usr/lib/ruby/gems/1.8/gems/termios-0.9.4 for inspection.<br />
ruby extconf.rb install termios\n
</div>
<p>It was looking for header files in <tt>/usr/lib/ruby/1.8/powerpc-darwin8.0</tt>, but they weren&#8217;t there; they were in <tt>/usr/lib/ruby/1.8/universal-darwin8.0</tt>.</p>
<h4>The Solution</h4>
<p>The solution was simple enough: create symlinks to the header files:</p>
<pre>$<span style="color: darkgreen">sudo ln -s ../universal-darwin8.0/*.h ./</span>
$<span style="color: darkgreen">sudo gem install termios</span></pre>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/02/03/installing-termios-gem-on-mac-os-x/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Free Website Monitoring</title>
		<link>http://www.eahanson.com/2007/01/14/free-website-monitoring/</link>
		<comments>http://www.eahanson.com/2007/01/14/free-website-monitoring/#comments</comments>
		<pubDate>Mon, 15 Jan 2007 02:31:17 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Services]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/weblog/?p=12</guid>
		<description><![CDATA[I was looking around for a good website monitoring service or script, and I found Montastic which is a free website monitoring service.]]></description>
			<content:encoded><![CDATA[<p>I was looking around for a good website monitoring service or script, and I found <a href="http://www.montastic.com/">Montastic</a> which is a free website monitoring service.</p>
<p>There&#8217;s even a <a href="http://widgets.yahoo.com/gallery/view.php?widget=38681">Monstatic Widget</a> for <a href="http://widgets.yahoo.com/">Yahoo Widgets</a>.</p>
<p>I only wish it had <a href="http://www.jabber.org">Jabber</a> notification in addition to email notification.</p>
<p>I also read about <a href="http://nubyonrails.topfunky.com/articles/2006/03/29/surviving-rails-1-1-with-server-monitoring">dwatch</a> but I figured a service would be easier.</p>
<p><strong>Update:</strong> I got an email from Arun who works for <a href="http://www.site24x7.com">Site24x7.com</a>. It seems to have more features than Montastic, such as being able to look for a particular keyword in the response (or not in the response), and nice looking reports. I&#8217;ll be trying them out alongside Montastic. (Unrelatedly, the makers of Site24x7 also make <a href="http://www.zoho.com/">Zoho</a>, a suite of productivty apps like a word processor, spreadsheet, etc. Somehow I&#8217;d never heard of them.)</p>
<p><strong>Update 2</strong>: My pals at <a href="http://pivotallabs.com/">Pivotal Labs</a> have released <a href="http://monitwitter.com/">Monitwitter</a>, which uses Twitter to show you the status of your servers. Brilliant!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/01/14/free-website-monitoring/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
