<?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; My Software</title>
	<atom:link href="http://www.eahanson.com/category/my-software/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>AppleScripts for Creating Podcast Playlists in iTunes</title>
		<link>http://www.eahanson.com/2009/03/16/applescripts-for-creating-podcast-playlists-in-itunes/</link>
		<comments>http://www.eahanson.com/2009/03/16/applescripts-for-creating-podcast-playlists-in-itunes/#comments</comments>
		<pubDate>Tue, 17 Mar 2009 03:02:10 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[AppleScript]]></category>
		<category><![CDATA[My Software]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=70</guid>
		<description><![CDATA[I have a 2nd generation iPod Shuffle that I use with the super-awesome Arriva iPod headphones: I love these headphones because I can wear them at the gym without having to deal with wires. I actually bought the shuffle because of these headphones. At the gym, I like to start out by listening to a [...]]]></description>
			<content:encoded><![CDATA[<p>I have a <a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&#038;location=http%3A%2F%2Fwww.amazon.com%2Fs%3Fie%3DUTF8%26rs%3D172282%26ref%255F%3Dsr%255Fnr%255Fn%255F2%26keywords%3Dapple%2520ipod%2520shuffle%2520%25282nd%2520Generation%2529%26bbn%3D172623%26qid%3D1237257133%26rnid%3D493964%26rh%3Di%253Aaps%252Ck%253Aapple%2520ipod%2520shuffle%2520%25282nd%2520Generation%2529%252Ci%253Aelectronics%252Cn%253A172282%252Cp%255F4%253AApple%252Cn%253A172623&#038;tag=wshlst-20&#038;linkCode=ur2&#038;camp=1789&#038;creative=390957">2nd generation iPod Shuffle</a><img src="https://www.assoc-amazon.com/e/ir?t=wshlst-20&#038;l=ur2&#038;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /> that I use with the super-awesome <a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&#038;location=http%3A%2F%2Fwww.amazon.com%2Fs%3Fie%3DUTF8%26x%3D0%26ref%255F%3Dnb%255Fss%255Fgw%26y%3D0%26field-keywords%3Darriva%2520ipod%2520shuffle%26url%3Dsearch-alias%253Daps&#038;tag=wshlst-20&#038;linkCode=ur2&#038;camp=1789&#038;creative=390957">Arriva iPod headphones</a><img src="https://www.assoc-amazon.com/e/ir?t=wshlst-20&#038;l=ur2&#038;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />:<br />
<a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&#038;location=http%3A%2F%2Fwww.amazon.com%2Fs%3Fie%3DUTF8%26x%3D0%26ref%255F%3Dnb%255Fss%255Fgw%26y%3D0%26field-keywords%3Darriva%2520ipod%2520shuffle%26url%3Dsearch-alias%253Daps&#038;tag=wshlst-20&#038;linkCode=ur2&#038;camp=1789&#038;creative=390957"></p>
<p><img src="http://www.eahanson.com/weblog/wp-content/uploads/2009/03/arriva.jpg" alt="arriva" title="arriva" width="199" height="145" class="aligncenter size-full wp-image-71" /></a></p>
<p>I love these headphones because I can wear them at the gym without having to deal with wires. I actually bought the shuffle because of these headphones. At the gym, I like to start out by listening to a few news podcasts while I warm up and then I like to hear some random music.</p>
<p>Unfortunately, version 8.1 of Apple&#8217;s iTunes removed the ability to autofill podcasts on the shuffle. (See <a href="http://discussions.apple.com/thread.jspa?threadID=1937830&#038;tstart=0">this thread on Apple&#8217;s support page</a> for example.)</p>
<p>Luckily, it can still be done with AppleScript. Here&#8217;s a script that copies all of the songs from my &#8220;Gym&#8221; playlist, including podcasts, to my shuffle:</p>
<pre>
tell application "iTunes"
  repeat with thisSource in sources
    if the name of thisSource = "Erik's Shuffle" then set myIpod to thisSource
  end repeat

  set destinationPlaylist to the first playlist in myIpod
  delete tracks in destinationPlaylist

  set sourcePlaylist to playlist "Gym"
  if the number of tracks in sourcePlaylist is greater than 0 then
    set shufflable of (every track of sourcePlaylist) to true
    duplicate every track of sourcePlaylist to destinationPlaylist
  end if
end tell
</pre>
<p>And here&#8217;s the full script that I use these days. It deletes everything on the shuffle, then adds the contents of three of my podcast playlists (which are &#8220;smart playlists&#8221; that only contain the most recent unplayed episode), and then adds one song from a short list of songs I like to start my workout with, and then finally adds 25 random songs from a collection of music that&#8217;s suitable for the gym.</p>
<pre>
on addRandomSongs(sourcePlaylistName, destinationPlaylist, songCount)
  tell application "iTunes"
    set sourcePlaylist to playlist sourcePlaylistName
    set alreadyUsedTrackNumbers to {}
    set numberOfTracksInSourcePlaylist to the number of tracks in sourcePlaylist
    repeat songCount times
      set trackNumber to (random number from 1 to numberOfTracksInSourcePlaylist)
      if alreadyUsedTrackNumbers does not contain trackNumber then
        duplicate (track trackNumber of sourcePlaylist) to destinationPlaylist
        copy trackNumber to the end of alreadyUsedTrackNumbers
      end if
    end repeat
  end tell
end addRandomSongs

on addPodcast(sourcePodcastName, destinationPlaylist)
  tell application "iTunes"
    set sourcePodcast to playlist sourcePodcastName
    if the number of tracks in sourcePodcast is greater than 0 then
      set shufflable of (every track of sourcePodcast) to true
      duplicate every track of sourcePodcast to destinationPlaylist
    end if
  end tell
end addPodcast

on findDevice(deviceName)
  tell application "iTunes"
    repeat with thisSource in sources
      if the name of thisSource = deviceName then return thisSource
    end repeat
  end tell
end findDevice

--
-- begin
--

set myShuffle to findDevice("Erik's Shuffle")

-- delete all tracks from the shuffle
tell application "iTunes"
  set shufflePlaylist to the first playlist in myShuffle
  delete tracks in shufflePlaylist
end tell

-- add podcasts
addPodcast("NPR Hourly News Summary", shufflePlaylist)
addPodcast("California Report", shufflePlaylist)
addPodcast("NPR Story Of The Day", shufflePlaylist)

-- add songs
addRandomSongs("Gym Kickoff", shufflePlaylist, 1)
addRandomSongs("Gym Music", shufflePlaylist, 25)
</pre>
<p>To use either of these scripts, fire up ScriptEditor, paste the script in, choose &#8220;Save As&#8221; from the &#8220;File&#8221; menu and choose &#8220;Application&#8221; as the file format, and then save it in your Library/Scripts/Applications/iTunes folder. A script menu will appear in the right portion of your menu bar when you&#8217;re in iTunes.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2009/03/16/applescripts-for-creating-podcast-playlists-in-itunes/feed/</wfw:commentRss>
		<slash:comments>4</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>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>Introducing wshlst.com</title>
		<link>http://www.eahanson.com/2007/10/24/wshlstcom/</link>
		<comments>http://www.eahanson.com/2007/10/24/wshlstcom/#comments</comments>
		<pubDate>Wed, 24 Oct 2007 19:06:57 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[My Software]]></category>
		<category><![CDATA[wshlst.com]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/2007/10/24/wshlstcom/</guid>
		<description><![CDATA[wshlst.com is a wish list website I created for my family to use. It differs from other wish list sites in a few ways: it&#8217;s group-oriented, it allows people to add items to other peoples&#8217; wish lists (secretly, if desired), and it allows people to discuss wish list items (again, secretly, if desired). The result [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.wshlst.com/">wshlst.com</a> is a wish list website I created for my family to use. </p>
<p>It differs from other wish list sites in a few ways: it&#8217;s group-oriented, it allows people to add items to other peoples&#8217; wish lists (secretly, if desired), and it allows people to discuss wish list items (again, secretly, if desired). The result is a site that lets a group of people plot and scheme behind each others&#8217; backs to make gift buying easier.</p>
<h3>Site Tour</h3>
<p><img src="/images/wshlst/overview.jpg" width="400" height="220" class="articleImage"></p>
<p>The site&#8217;s main page is divided into three panels. The first is called &#8220;People&#8221; and lists all the people you share wish lists with. For me, it lists everyone in my family and everyone in my wife&#8217;s family. </p>
<p><img src="/images/wshlst/people.jpg" width="311" height="197" class="articleImage"></p>
<p>Clicking on a person loads their wish list in the next panel, the &#8220;Wish List&#8221; panel. As you&#8217;d expect, you can add and remove items from your wish list. But you can also add and remove items from other peoples&#8217; wish lists. By default, when you add an item to someone else&#8217;s list, it&#8217;s not visible to that person. Items can also be marked as &#8220;Claimed&#8221; so two people don&#8217;t end up buying the same gift. </p>
<p><img src="/images/wshlst/wishlist.jpg" width="313" height="289" class="articleImage"></p>
<p>Clicking on a wish list item loads comments about the item in the third panel, called &#8220;Discussion&#8221;. Comments in the discussion panel are also hidden from the item&#8217;s intended recipient by default. </p>
<p><img src="/images/wshlst/discussion.jpg" width="312" height="150" class="articleImage"></p>
<h3>Try It Out</h3>
<p>There&#8217;s no way to sign up to use the site (yet&mdash;if there&#8217;s interest from anyone outside my family, I might take the time to add the ability), but there are demo users you can use to try the site out. Details are on the front page of the site: <a href="http://www.wshlst.com/">www.wshlst.com</a>.</p>
<p><i><a href="http://www.eahanson.com/category/wshlstcom/">Read more posts about wshlst.com</a></i></p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/10/24/wshlstcom/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>A Mac OS X Web Browser for JavaScript Testing</title>
		<link>http://www.eahanson.com/2005/10/02/writing-a-mac-os-x-web-browser-for-javascript-testing/</link>
		<comments>http://www.eahanson.com/2005/10/02/writing-a-mac-os-x-web-browser-for-javascript-testing/#comments</comments>
		<pubDate>Sun, 02 Oct 2005 21:34:22 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[My Software]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/weblog/?p=5</guid>
		<description><![CDATA[I was playing with a very JavaScript-heavy web page and wanted my automated tests to use a real web browser. I tried running them inside Safari, but it's a bit annoying to have my tests take control of my browser. Plus, there were caching issues. So I wrote a very simple web browser.]]></description>
			<content:encoded><![CDATA[<p>I was playing with a very JavaScript-heavy web page and wanted my automated tests to use a real web browser. I tried running them inside Safari, but it&#8217;s a bit annoying to have my tests take control of my browser. Plus, there were caching issues. So I wrote a very simple web browser with the following features:</p>
<ul>
<li>It uses WebKit so it works just like Safari</li>
<li>It doesn&#8217;t do any caching</li>
<li>It takes a URL command-line parameter and loads it</li>
<li>It quits immediately <b>[update: I added a noquit paramter in case you don't want it to quit immediately]</b></li>
</ul>
<p>It currently has the following problems:</p>
<ul>
<li>It&#8217;s uses AppKit, so it has a UI. I really wanted it to be a command-line-only app, but I haven&#8217;t yet figured out how to do that</li>
<li>Invoking it is painful: <tt>/Applications/wkget.app/Contents/MacOS/wkget -url http://www.apple.com</tt></li>
<li>It only tests WebKit; one day I might make a version that uses Firefox&#8217;s JavaScript engine</li>
</ul>
<p>I called it <b>wkget</b> and you can <a href="/sw/wkget.zip">download it here</a>. [<b>Update:</b> I had an incorrectly-built version up here for a while. It's fixed now.]</p>
<p>Here&#8217;s the code (sorry for the formatting; I had to wrap everything for this very narrow column):</p>
<pre>
@implementation WkgetController
- (void)awakeFromNib {
  [self gotoUrl:self];
  [webView setFrameLoadDelegate:self];
}

- (IBAction)gotoUrl:(id)sender {
  NSString *url = [[NSUserDefaults standardUserDefaults]
      stringForKey:@"url"];

  if (url == nil) {
    url = @"http://www.apple.com/";
  }

  NSURLRequest *request = [NSURLRequest
      requestWithURL:[NSURL URLWithString:url]
      cachePolicy:NSURLRequestReloadIgnoringCacheData
      timeoutInterval:1.0];

  [[webView mainFrame] loadRequest:request];
}

- (void)webView:(WebView *)sender
  didFinishLoadForFrame:(WebFrame *)frame {
  if (![[NSUserDefaults standardUserDefaults]
      boolForKey:@"noquit"]) {
    [NSApp terminate:self];
  }
}
@end
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2005/10/02/writing-a-mac-os-x-web-browser-for-javascript-testing/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
