<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" /><style type="text/css"><!--
#msg dl { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre, #msg p { overflow: auto; background: #ffc; border: 1px #fc0 solid; padding: 6px; }
#msg ul { overflow: auto; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<title>[573] trunk/rails: Formatted the catalog/show view</title>
</head>
<body>

<div id="msg">
<dl>
<dt>Revision</dt> <dd>573</dd>
<dt>Author</dt> <dd>mwmitchell</dd>
<dt>Date</dt> <dd>2008-05-27 17:24:07 -0400 (Tue, 27 May 2008)</dd>
</dl>

<h3>Log Message</h3>
<pre>Formatted the catalog/show view
Added helper methods to automatically load css and js that match the controller name
Added new helper calls to layouts/application.html.erb
Wrapped link to show action around cover image in index view
Removed un-used Prototype JS library files</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkrailsCHANGELOGtxt">trunk/rails/CHANGELOG.txt</a></li>
<li><a href="#trunkrailsapphelpersapplication_helperrb">trunk/rails/app/helpers/application_helper.rb</a></li>
<li><a href="#trunkrailsappviewscatalog_index_partials_lib_cataloghtmlerb">trunk/rails/app/views/catalog/_index_partials/_lib_catalog.html.erb</a></li>
<li><a href="#trunkrailsappviewscatalog_show_partials_lib_cataloghtmlerb">trunk/rails/app/views/catalog/_show_partials/_lib_catalog.html.erb</a></li>
<li><a href="#trunkrailsappviewscatalogshowhtmlerb">trunk/rails/app/views/catalog/show.html.erb</a></li>
<li><a href="#trunkrailsappviewslayoutsapplicationhtmlerb">trunk/rails/app/views/layouts/application.html.erb</a></li>
<li><a href="#trunkrailslibuvavirgo_marc_recordrb">trunk/rails/lib/uva/virgo_marc_record.rb</a></li>
<li><a href="#trunkrailslibuvaz3950rb">trunk/rails/lib/uva/z3950.rb</a></li>
<li><a href="#trunkrailspublicjavascriptsapplicationjs">trunk/rails/public/javascripts/application.js</a></li>
<li><a href="#trunkrailspublicjavascriptscatalogindexjs">trunk/rails/public/javascripts/catalog/index.js</a></li>
<li><a href="#trunkrailspublicjavascriptscatalogshowjs">trunk/rails/public/javascripts/catalog/show.js</a></li>
<li><a href="#trunkrailsvendorgemsblacklightlibblacklightservicewikipediarb">trunk/rails/vendor/gems/blacklight/lib/blacklight/service/wikipedia.rb</a></li>
<li><a href="#trunkrailsvendorgemsflarelibflareview_helpercommonrb">trunk/rails/vendor/gems/flare/lib/flare/view_helper/common.rb</a></li>
<li><a href="#trunkrailsvendorgemsflarelibflareview_helperurlrb">trunk/rails/vendor/gems/flare/lib/flare/view_helper/url.rb</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkrailspublicstylesheetsapplicationcss">trunk/rails/public/stylesheets/application.css</a></li>
</ul>

<h3>Removed Paths</h3>
<ul>
<li><a href="#trunkrailspublicjavascriptscontrolsjs">trunk/rails/public/javascripts/controls.js</a></li>
<li><a href="#trunkrailspublicjavascriptsdragdropjs">trunk/rails/public/javascripts/dragdrop.js</a></li>
<li><a href="#trunkrailspublicjavascriptseffectsjs">trunk/rails/public/javascripts/effects.js</a></li>
<li><a href="#trunkrailspublicjavascriptsmainjs">trunk/rails/public/javascripts/main.js</a></li>
<li><a href="#trunkrailspublicjavascriptsprototypejs">trunk/rails/public/javascripts/prototype.js</a></li>
<li><a href="#trunkrailspublicstylesheetsmaincss">trunk/rails/public/stylesheets/main.css</a></li>
<li><a href="#trunkrailspublicstylesheetsshowcss">trunk/rails/public/stylesheets/show.css</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkrailsCHANGELOGtxt"></a>
<div class="modfile"><h4>Modified: trunk/rails/CHANGELOG.txt (572 => 573)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/rails/CHANGELOG.txt        2008-05-27 16:17:02 UTC (rev 572)
+++ trunk/rails/CHANGELOG.txt        2008-05-27 21:24:07 UTC (rev 573)
</span><span class="lines">@@ -34,4 +34,4 @@
</span><span class="cx">     UVa's implementation of Blacklight will drive the development of the Flare and Blacklight Gems.
</span><span class="cx">       * They are located in vendor/gems.
</span><span class="cx">     Support for other Ruby frameworks is a possibility (Merb, Camping etc.)
</span><del>-          
</del><span class="cx">\ No newline at end of file
</span><ins>+          More documentation and tests
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="trunkrailsapphelpersapplication_helperrb"></a>
<div class="modfile"><h4>Modified: trunk/rails/app/helpers/application_helper.rb (572 => 573)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/rails/app/helpers/application_helper.rb        2008-05-27 16:17:02 UTC (rev 572)
+++ trunk/rails/app/helpers/application_helper.rb        2008-05-27 21:24:07 UTC (rev 573)
</span><span class="lines">@@ -5,6 +5,14 @@
</span><span class="cx"> 
</span><span class="cx"> module ApplicationHelper
</span><span class="cx">   
</span><ins>+  def load_controller_stylesheet
+    stylesheet_link_tag(params[:controller]) if File.file?(&quot;public/stylesheets/#{params[:controller]}.css&quot;)
+  end
+  
+  def load_controller_javascript
+    javascript_include_tag(params[:controller]) if File.file?(&quot;public/javascripts/#{params[:controller]}.js&quot;)
+  end
+  
</ins><span class="cx">   def zotero_export_url(document)
</span><span class="cx">     #http://blog.reciprocallattice.com/2007/12/add-unapi-support-in-rails-application.html
</span><span class="cx">   end
</span></span></pre></div>
<a id="trunkrailsappviewscatalog_index_partials_lib_cataloghtmlerb"></a>
<div class="modfile"><h4>Modified: trunk/rails/app/views/catalog/_index_partials/_lib_catalog.html.erb (572 => 573)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/rails/app/views/catalog/_index_partials/_lib_catalog.html.erb        2008-05-27 16:17:02 UTC (rev 572)
+++ trunk/rails/app/views/catalog/_index_partials/_lib_catalog.html.erb        2008-05-27 21:24:07 UTC (rev 573)
</span><span class="lines">@@ -1,9 +1,13 @@
</span><span class="cx"> &lt;div class=&quot;document layoutContainer&quot; id=&quot;Doc&lt;%= document['id'] %&gt;&quot;&gt;
</span><span class="cx">         
</span><span class="cx">         &lt;div class=&quot;left&quot;&gt;
</span><ins>+                
+                &lt;a href=&quot;&lt;%= document_resource_path(document['id']) %&gt;&quot;&gt;
</ins><span class="cx">                 &lt;span class=&quot;coverImage&quot; title=&quot;&lt;%= document['isbn_display'].collect{|i|i.match(/[0-9-]+/)}.join(',') rescue '' %&gt;&quot;&gt;
</span><span class="cx">                 &lt;%= image_tag 'ajax-loader.gif', :class=&gt;'ajaxLoader' %&gt;
</span><ins>+                
</ins><span class="cx">                 &lt;/span&gt;
</span><ins>+                &lt;/a&gt;
</ins><span class="cx">         &lt;/div&gt;
</span><span class="cx">         
</span><span class="cx">         &lt;div class=&quot;right&quot;&gt;
</span></span></pre></div>
<a id="trunkrailsappviewscatalog_show_partials_lib_cataloghtmlerb"></a>
<div class="modfile"><h4>Modified: trunk/rails/app/views/catalog/_show_partials/_lib_catalog.html.erb (572 => 573)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/rails/app/views/catalog/_show_partials/_lib_catalog.html.erb        2008-05-27 16:17:02 UTC (rev 572)
+++ trunk/rails/app/views/catalog/_show_partials/_lib_catalog.html.erb        2008-05-27 21:24:07 UTC (rev 573)
</span><span class="lines">@@ -1,31 +1,123 @@
</span><del>-&lt;table class=&quot;item_info&quot;&gt;
-&lt;tr class=&quot;title&quot;&gt;
-        &lt;td class=&quot;key&quot;&gt;Title&lt;/td&gt;
-        &lt;td class=&quot;value&quot;&gt;&lt;%= document_field_value @document, :title_display %&gt;&lt;/td&gt;
</del><ins>+&lt;%## need a better way to handle js and css dependencies... %&gt;
</ins><span class="cx"> 
</span><del>-        &lt;tr&gt;
-                &lt;td class=&quot;key&quot;&gt;Author&lt;/td&gt;
-                &lt;td class=&quot;value&quot;&gt;&lt;%= document_field_value(@document, :author_display) or 'n/a' %&gt;&lt;/td&gt;
-        &lt;/tr&gt;
</del><ins>+&lt;% if request.env['HTTP_X_REQUESTED_WITH']=='XMLHttpRequest' %&gt;
+        &lt;%= javascript_include_tag 'application' %&gt;
+        &lt;%= stylesheet_link_tag 'application' %&gt;
+&lt;% end %&gt;
</ins><span class="cx"> 
</span><del>-        &lt;tr&gt;
-                &lt;td class=&quot;key&quot;&gt;Call Number&lt;/td&gt;
-                &lt;td class=&quot;value&quot;&gt;&lt;%= document_field_value(@document, :call_number_display) or 'n/a' %&gt;&lt;/td&gt;
-        &lt;/tr&gt;
</del><ins>+&lt;style&gt;
+        .document .right{
+        width:78%;
+        }
+&lt;/style&gt;
+
+&lt;% marc=UVA::VirgoMarcRecord.new(document['marc_display'], :xml) %&gt;
+
+&lt;%### marc.fields.join('&lt;br/&gt;') %&gt;
+
+&lt;div class=&quot;document layoutContainer&quot; id=&quot;Doc&lt;%= document['id'] %&gt;&quot;&gt;
</ins><span class="cx">         
</span><del>-        &lt;tr&gt;
-                &lt;td class=&quot;key&quot;&gt;Format&lt;/td&gt;
-                &lt;td class=&quot;value&quot;&gt;&lt;%= document_field_value(@document, :format_facet) or 'n/a' %&gt;&lt;/td&gt;
-        &lt;/tr&gt;
</del><ins>+        &lt;div class=&quot;left&quot;&gt;
+                &lt;span class=&quot;coverImage&quot; title=&quot;&lt;%= document['isbn_display'].collect{|i|i.match(/[0-9-]+/)}.join(',') rescue '' %&gt;&quot;&gt;
+                &lt;%= image_tag 'ajax-loader.gif', :class=&gt;'ajaxLoader' %&gt;
+                &lt;/span&gt;
+        &lt;/div&gt;
</ins><span class="cx">         
</span><del>-        &lt;tr&gt;
-                &lt;td class=&quot;key&quot;&gt;ISBN&lt;/td&gt;
-                &lt;td class=&quot;value&quot;&gt;&lt;%= document_field_value(@document, :isbn_display) or 'n/a' %&gt;&lt;/td&gt;
-        &lt;/tr&gt;
-&lt;/table&gt;
</del><ins>+        &lt;div class=&quot;right&quot;&gt;
+                &lt;dl&gt;
+                        &lt;div&gt;
+                                &lt;dt&gt;Title: &lt;/dt&gt;
+                                &lt;dd class=&quot;documentTitle&quot;&gt;&lt;%= document_field_value(document, :title_display) or 'n/a' %&gt;&lt;/dd&gt;
+                        &lt;/div&gt;
+                        
+                        &lt;div&gt;
+                                &lt;dt&gt;Author&lt;/dt&gt;
+                                &lt;dd&gt;
+                                        &lt;%= document_field_value(document, :author_display) or 'n/a' %&gt;
+                                &lt;/dd&gt;
+                        &lt;/div&gt;
+                        
+                        &lt;!--
+                        &lt;div&gt;
+                                &lt;dt&gt;Also called:&lt;/dt&gt;
+                                &lt;dd&gt;&amp;nbsp;&lt;/dd&gt;
+                        &lt;/div&gt;
+                        
+                        &lt;div&gt;
+                                &lt;dt&gt;Includes:&lt;/dt&gt;
+                                &lt;dd&gt;&amp;nbsp;&lt;/dd&gt;
+                        &lt;/div&gt;
+                        --&gt;
+                        
+                        &lt;div&gt;
+                                &lt;dt&gt;Edition&lt;/dt&gt;
+                                &lt;dd&gt;&lt;%= marc['250']['a'] rescue 'n/a' %&gt;&lt;/dd&gt;
+                        &lt;/div&gt;
+                        
+                        &lt;div&gt;
+                                &lt;dt&gt;Publication&lt;/dt&gt;
+                                &lt;dd&gt;&lt;%= marc['260'].subfields.map(&amp;:value).join(' ') rescue 'n/a' %&gt;&lt;/dd&gt;
+                        &lt;/div&gt;
+                        
+                        &lt;div&gt;
+                                &lt;dt&gt;Subjects&lt;/dt&gt;
+                                &lt;dd&gt;
+                                        &lt;% subject_subfields = ((marc['610'].subfields.collect rescue []) + (marc['650'].subfields.collect.to_a rescue [])) %&gt;
+                                        &lt;%=
+                                        subject_subfields.collect do |sf|
+                                                link_to sf.value, catalog_index_path(params_for_adding_search_query(sf.value, false))
+                                        end.join(' | ')
+                                        %&gt;&amp;nbsp;
+                                &lt;/dd&gt;
+                        &lt;/div&gt;
+                        
+                        &lt;div&gt;
+                                &lt;dt&gt;Format&lt;/dt&gt;
+                                &lt;dd&gt;
+                                        &lt;%= document['format_facet'].collect do |f|
+                                                link_to(
+                                                        field_to_label(f),
+                                                        catalog_index_path(params_for_adding_search_filter('format_facet', f), false)
+                                                )
+                                        end.join(' | ') rescue 'n/a' %&gt;
+                                        &amp;nbsp;
+                                &lt;/dd&gt;
+                        &lt;/div&gt;
+                        
+                        &lt;div&gt;
+                                &lt;dt&gt;Location&lt;/dt&gt;
+                                &lt;dd&gt;
+                                        &lt;%= document['location_facet'].collect do |f|
+                                                link_to(
+                                                        field_to_label(f),
+                                                        catalog_index_path(params_for_adding_search_filter('location_facet', f), false)
+                                                )
+                                        end.join(' | ') rescue 'n/a' %&gt;
+                                        &amp;nbsp;
+                        &lt;/div&gt;
+                        
+                        &lt;div&gt;
+                                &lt;dt&gt;Call Number&lt;/dt&gt;
+                                &lt;dd&gt;
+          &lt;%= document_field_value(document, :call_number_display, '&lt;br/&gt;') or 'n/a' %&gt;
+                                &lt;/dd&gt;
+                        &lt;/div&gt;
+                        
+                        &lt;div&gt;
+                                &lt;dt&gt;ISBN&lt;/dt&gt;
+                                &lt;dd&gt;
+                                  &lt;%= document_field_value(document, :isbn_display, '&lt;br/&gt;') or 'n/a' %&gt;
+                                &lt;/dd&gt;
+                        &lt;/div&gt;
+                        
+                &lt;/dl&gt;
+        
+        &lt;/div&gt;
+        
+&lt;/div&gt;
</ins><span class="cx"> 
</span><span class="cx"> &lt;% unless document['format_facet'].to_a.include? 'Internet' %&gt;
</span><del>-&lt;div id=&quot;LibraryItemAvailability&quot;&gt;
</del><ins>+&lt;div id=&quot;LibraryItemAvailability&quot; title=&quot;&lt;%= document['id'] %&gt;&quot;&gt;
</ins><span class="cx">   &lt;%= image_tag 'ajax-loader.gif' %&gt;
</span><span class="cx"> &lt;/div&gt;
</span><span class="cx"> &lt;% end %&gt;
</span><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="trunkrailsappviewscatalogshowhtmlerb"></a>
<div class="modfile"><h4>Modified: trunk/rails/app/views/catalog/show.html.erb (572 => 573)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/rails/app/views/catalog/show.html.erb        2008-05-27 16:17:02 UTC (rev 572)
+++ trunk/rails/app/views/catalog/show.html.erb        2008-05-27 21:24:07 UTC (rev 573)
</span><span class="lines">@@ -16,24 +16,14 @@
</span><span class="cx"> ##########################################################################
</span><span class="cx"> --&gt;
</span><span class="cx"> 
</span><ins>+&lt;%### HTML here should be generic enough to handle all types of items - add custom HTML to the &quot;view_type&quot; partials %&gt;
+
</ins><span class="cx"> &lt;% view_type=partial_type_for_document(@document) %&gt;
</span><span class="cx"> 
</span><del>-&lt;%= stylesheet_link_tag 'show.css' %&gt;
</del><span class="cx"> &lt;%= javascript_include_tag 'jquery.form.js' %&gt;
</span><span class="cx"> &lt;%= javascript_include_tag 'catalog/show' %&gt;
</span><del>-&lt;%= javascript_include_tag 'catalog/index' %&gt;
</del><span class="cx"> 
</span><del>-
-
-
</del><span class="cx"> &lt;div class=&quot;itemShowView &lt;%= view_type %&gt;&quot; id=&quot;Doc&lt;%= @document['id'] %&gt;&quot;&gt;
</span><del>-
-&lt;!-- Display the book cover --&gt;
-&lt;div class=&quot;left&quot;&gt;
-        &lt;span class=&quot;coverImage&quot; title=&quot;&lt;%= @document['isbn_display'].collect{|i|i.match(/[0-9-]+/)}.join(',') rescue '' %&gt;&quot;&gt;
-        &lt;%= image_tag 'ajax-loader.gif', :class=&gt;'ajaxLoader' %&gt;
-        &lt;/span&gt;
-&lt;/div&gt;
</del><span class="cx">   
</span><span class="cx">   &lt;!-- all of the content is controlled by the partial view --&gt;
</span><span class="cx">   &lt;%= render :partial=&gt;&quot;catalog/_show_partials/#{view_type}&quot;, :locals=&gt;{:document=&gt;@document} %&gt;
</span></span></pre></div>
<a id="trunkrailsappviewslayoutsapplicationhtmlerb"></a>
<div class="modfile"><h4>Modified: trunk/rails/app/views/layouts/application.html.erb (572 => 573)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/rails/app/views/layouts/application.html.erb        2008-05-27 16:17:02 UTC (rev 572)
+++ trunk/rails/app/views/layouts/application.html.erb        2008-05-27 21:24:07 UTC (rev 573)
</span><span class="lines">@@ -47,10 +47,13 @@
</span><span class="cx">                         var USER_BOOKMARKS_PATH='&lt;%= @current_user ? user_bookmarks_path(@current_user) : 'null' %&gt;';
</span><span class="cx">                 &lt;/script&gt;
</span><span class="cx">                 
</span><del>-                &lt;%= stylesheet_link_tag 'main.css' %&gt;
</del><ins>+                &lt;%= stylesheet_link_tag 'application' %&gt;
</ins><span class="cx">                 &lt;%= javascript_include_tag 'jquery-1.2.3.min.js' %&gt;
</span><del>-                &lt;%= javascript_include_tag 'main' %&gt;
</del><ins>+                &lt;%= javascript_include_tag 'application' %&gt;
</ins><span class="cx">                 
</span><ins>+                &lt;%= load_controller_stylesheet %&gt;
+                &lt;%= load_controller_javascript %&gt;
+                
</ins><span class="cx">         &lt;/head&gt;
</span><span class="cx">         
</span><span class="cx">         &lt;% # if were in the &quot;users&quot; resource area, switch to a single column view %&gt;
</span></span></pre></div>
<a id="trunkrailslibuvavirgo_marc_recordrb"></a>
<div class="modfile"><h4>Modified: trunk/rails/lib/uva/virgo_marc_record.rb (572 => 573)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/rails/lib/uva/virgo_marc_record.rb        2008-05-27 16:17:02 UTC (rev 572)
+++ trunk/rails/lib/uva/virgo_marc_record.rb        2008-05-27 21:24:07 UTC (rev 573)
</span><span class="lines">@@ -5,8 +5,14 @@
</span><span class="cx">   
</span><span class="cx">   attr_reader :record
</span><span class="cx">   
</span><del>-  def initialize(marc)
-    @record = MARC::Record.new_from_marc(marc, :forgiving=&gt;true)
</del><ins>+  def initialize(marc, format=:raw)
+    marc=marc.to_s
+    if format==:xml
+      reader = MARC::XMLReader.new(StringIO.new(marc))
+      @record=reader.detect{|r|true}
+    else
+      @record = MARC::Record.new_from_marc(marc, :forgiving=&gt;true)
+    end
</ins><span class="cx">   end
</span><span class="cx">   
</span><span class="cx">   def available?
</span></span></pre></div>
<a id="trunkrailslibuvaz3950rb"></a>
<div class="modfile"><h4>Modified: trunk/rails/lib/uva/z3950.rb (572 => 573)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/rails/lib/uva/z3950.rb        2008-05-27 16:17:02 UTC (rev 572)
+++ trunk/rails/lib/uva/z3950.rb        2008-05-27 21:24:07 UTC (rev 573)
</span><span class="lines">@@ -75,6 +75,13 @@
</span><span class="cx">     
</span><span class="cx">       def fetch_all_items(method, id)
</span><span class="cx">         data = {}
</span><ins>+        
+        data[:title]=''
+        data[:author]=''
+        data[:isbn]=''
+        data[:holdings]=[]
+        data[:summary_holdings]=[]
+        
</ins><span class="cx">         query(method, id) do |rec,marc|
</span><span class="cx">           data[:title] = marc['245']['a'] rescue nil
</span><span class="cx">           data[:author] = marc['245']['c'] rescue nil
</span><span class="lines">@@ -83,7 +90,7 @@
</span><span class="cx">           data[:summary_holdings] = extract_summary_holdings(marc)
</span><span class="cx">         end
</span><span class="cx">         # If non of the items have the :copies key, then the Z3950 probably couldn't find any holdings info
</span><del>-        data[:is_available] = data[:holdings].all?{|i|i[:copies].nil?} ? -1 : data[:holdings].any?{|i|i[:copies].to_i&gt;0}
</del><ins>+        data[:is_available] = data[:holdings].all?{|i|i[:copies].nil?} ? -1 : data[:holdings].any?{|i|i[:copies].to_i&gt;0} rescue -1
</ins><span class="cx">         data
</span><span class="cx">       end
</span><span class="cx">     
</span></span></pre></div>
<a id="trunkrailspublicjavascriptsapplicationjs"></a>
<div class="modfile"><h4>Modified: trunk/rails/public/javascripts/application.js (572 => 573)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/rails/public/javascripts/application.js        2008-05-27 16:17:02 UTC (rev 572)
+++ trunk/rails/public/javascripts/application.js        2008-05-27 21:24:07 UTC (rev 573)
</span><span class="lines">@@ -0,0 +1,71 @@
</span><ins>+jQuery(function(){
+        
+        var $=jQuery;
+        
+        $.ajaxSetup({
+                'timeout':10000
+        });
+        
+        /*
+                This is used in the search lists (index) and the search item views (show)
+        */
+        (function(){
+                $('.addUserBookmarkForm .submitForm').click(function(e){
+                        e.preventDefault();
+                        var el=$(this);
+                        el.parent().ajaxSubmit({
+                                success:function(){
+                                        el.parent().html('This is in your &lt;a href=&quot;' + USER_BOOKMARKS_PATH + '&quot;&gt;Bookmarks&lt;/a&gt;')
+                                }
+                        });
+                })
+        })();
+        
+        /*
+                Load cover images
+                Sample HTML:
+                &lt;span class=&quot;coverImage&quot;&gt;ANYTHING HERE GETS REPLACED BY img TAG&lt;/span&gt;
+        */
+        (function(){
+                var isbn=null;
+                $('span.coverImage').each(function(i,span){
+                        var el=$(span);
+                        var isbn=el.attr('title');
+                        if( ! isbn || isbn=='' || isbn==null ){
+                                el.html('&lt;img src=&quot;/images/catalog/default_bookcover.gif&quot;/&gt;');
+                        }else{
+                                $.get('/bookcovers', {'isbn':isbn}, function(data){
+                                        el.html(data);
+                                });
+                        }
+                });
+        })();
+        
+        /*
+                Bind the availability links to a handler
+                
+                SAMPLE CSS:
+                .hide{display:none;}
+                
+                SAMPLE HTML:
+                &lt;div class=&quot;document&quot; id=&quot;DocXXX&quot;&gt;
+                        &lt;div&gt;
+                                &lt;img src=&quot;loader.gif&quot; class=&quot;ajaxLoader hide&quot;/&gt;
+                                &lt;a href=&quot;#&quot; class=&quot;availability&quot;&gt;Check Availability...&lt;/a&gt;
+                        &lt;/div&gt;
+                &lt;/div&gt;
+        */
+        (function(){
+                $('.availability a').click(function(e){
+                        e.preventDefault();
+                        var el=$(this);
+                        el.addClass('hide');
+                        $('.ajaxLoader', this.parentNode).removeClass('hide');
+                        var id = el.parents('.document').attr('id').replace(/^Doc/, '');
+                        $.get('/virgo/availability', {ckey:id, no_layout:true}, function(data){
+                                el.parent().html(data);
+                        });
+                });
+        })();
+        
+});
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="trunkrailspublicjavascriptscatalogindexjs"></a>
<div class="modfile"><h4>Modified: trunk/rails/public/javascripts/catalog/index.js (572 => 573)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/rails/public/javascripts/catalog/index.js        2008-05-27 16:17:02 UTC (rev 572)
+++ trunk/rails/public/javascripts/catalog/index.js        2008-05-27 21:24:07 UTC (rev 573)
</span><span class="lines">@@ -7,40 +7,6 @@
</span><span class="cx">         }
</span><span class="cx">         
</span><span class="cx">         /*
</span><del>-                Load cover images
-        */
-        (function(){
-                var isbn=null;
-                $('span.coverImage').each(function(i,span){
-                        var el=$(span);
-                        var isbn=el.attr('title');
-                        if( ! isbn || isbn=='' || isbn==null ){
-                                el.html('&lt;img src=&quot;/images/catalog/default_bookcover.gif&quot;/&gt;');
-                        }else{
-                                $.get('/bookcovers', {'isbn':isbn}, function(data){
-                                        el.html(data);
-                                });
-                        }
-                });
-        })();
-        
-        /*
-                Bind the availability links to a handler
-        */
-        (function(){
-                $('.availability a').click(function(e){
-                        e.preventDefault();
-                        var el=$(this);
-                        el.addClass('hide');
-                        $('.ajaxLoader', this.parentNode).removeClass('hide');
-                        var id = el.parents('.document').attr('id').replace(/^Doc/, '');
-                        $.get('/virgo/availability', {ckey:id, no_layout:true}, function(data){
-                                el.parent().html(data);
-                        });
-                });
-        })();
-        
-        /*
</del><span class="cx">                 Bind preview links to modal window handler
</span><span class="cx">         */
</span><span class="cx">         (function(){
</span></span></pre></div>
<a id="trunkrailspublicjavascriptscatalogshowjs"></a>
<div class="modfile"><h4>Modified: trunk/rails/public/javascripts/catalog/show.js (572 => 573)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/rails/public/javascripts/catalog/show.js        2008-05-27 16:17:02 UTC (rev 572)
+++ trunk/rails/public/javascripts/catalog/show.js        2008-05-27 21:24:07 UTC (rev 573)
</span><span class="lines">@@ -6,7 +6,7 @@
</span><span class="cx">         (function(){
</span><span class="cx">                 var libItemAvail=$('#LibraryItemAvailability');
</span><span class="cx">                 if(libItemAvail.length &gt; 0){
</span><del>-                        var id = $('.itemShowView',libItemAvail.parents()).attr('id').replace(/^Doc/, '');
</del><ins>+                        var id = libItemAvail.attr('title');
</ins><span class="cx">                         $.get('/virgo/status', {ckey:id, no_layout:true}, function(data){
</span><span class="cx">                                 libItemAvail.html(data);
</span><span class="cx">                         });
</span></span></pre></div>
<a id="trunkrailspublicjavascriptscontrolsjs"></a>
<div class="delfile"><h4>Deleted: trunk/rails/public/javascripts/controls.js (572 => 573)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/rails/public/javascripts/controls.js        2008-05-27 16:17:02 UTC (rev 572)
+++ trunk/rails/public/javascripts/controls.js        2008-05-27 21:24:07 UTC (rev 573)
</span><span class="lines">@@ -1,963 +0,0 @@
</span><del>-// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
-//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
-// Contributors:
-//  Richard Livsey
-//  Rahul Bhargava
-//  Rob Wills
-// 
-// script.aculo.us is freely distributable under the terms of an MIT-style license.
-// For details, see the script.aculo.us web site: http://script.aculo.us/
-
-// Autocompleter.Base handles all the autocompletion functionality 
-// that's independent of the data source for autocompletion. This
-// includes drawing the autocompletion menu, observing keyboard
-// and mouse events, and similar.
-//
-// Specific autocompleters need to provide, at the very least, 
-// a getUpdatedChoices function that will be invoked every time
-// the text inside the monitored textbox changes. This method 
-// should get the text for which to provide autocompletion by
-// invoking this.getToken(), NOT by directly accessing
-// this.element.value. This is to allow incremental tokenized
-// autocompletion. Specific auto-completion logic (AJAX, etc)
-// belongs in getUpdatedChoices.
-//
-// Tokenized incremental autocompletion is enabled automatically
-// when an autocompleter is instantiated with the 'tokens' option
-// in the options parameter, e.g.:
-// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
-// will incrementally autocomplete with a comma as the token.
-// Additionally, ',' in the above example can be replaced with
-// a token array, e.g. { tokens: [',', '\n'] } which
-// enables autocompletion on multiple tokens. This is most 
-// useful when one of the tokens is \n (a newline), as it 
-// allows smart autocompletion after linebreaks.
-
-if(typeof Effect == 'undefined')
-  throw(&quot;controls.js requires including script.aculo.us' effects.js library&quot;);
-
-var Autocompleter = { }
-Autocompleter.Base = Class.create({
-  baseInitialize: function(element, update, options) {
-    element          = $(element)
-    this.element     = element; 
-    this.update      = $(update);  
-    this.hasFocus    = false; 
-    this.changed     = false; 
-    this.active      = false; 
-    this.index       = 0;     
-    this.entryCount  = 0;
-    this.oldElementValue = this.element.value;
-
-    if(this.setOptions)
-      this.setOptions(options);
-    else
-      this.options = options || { };
-
-    this.options.paramName    = this.options.paramName || this.element.name;
-    this.options.tokens       = this.options.tokens || [];
-    this.options.frequency    = this.options.frequency || 0.4;
-    this.options.minChars     = this.options.minChars || 1;
-    this.options.onShow       = this.options.onShow || 
-      function(element, update){ 
-        if(!update.style.position || update.style.position=='absolute') {
-          update.style.position = 'absolute';
-          Position.clone(element, update, {
-            setHeight: false, 
-            offsetTop: element.offsetHeight
-          });
-        }
-        Effect.Appear(update,{duration:0.15});
-      };
-    this.options.onHide = this.options.onHide || 
-      function(element, update){ new Effect.Fade(update,{duration:0.15}) };
-
-    if(typeof(this.options.tokens) == 'string') 
-      this.options.tokens = new Array(this.options.tokens);
-    // Force carriage returns as token delimiters anyway
-    if (!this.options.tokens.include('\n'))
-      this.options.tokens.push('\n');
-
-    this.observer = null;
-    
-    this.element.setAttribute('autocomplete','off');
-
-    Element.hide(this.update);
-
-    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
-    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
-  },
-
-  show: function() {
-    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
-    if(!this.iefix &amp;&amp; 
-      (Prototype.Browser.IE) &amp;&amp;
-      (Element.getStyle(this.update, 'position')=='absolute')) {
-      new Insertion.After(this.update, 
-       '&lt;iframe id=&quot;' + this.update.id + '_iefix&quot; '+
-       'style=&quot;display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);&quot; ' +
-       'src=&quot;javascript:false;&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;');
-      this.iefix = $(this.update.id+'_iefix');
-    }
-    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
-  },
-  
-  fixIEOverlapping: function() {
-    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
-    this.iefix.style.zIndex = 1;
-    this.update.style.zIndex = 2;
-    Element.show(this.iefix);
-  },
-
-  hide: function() {
-    this.stopIndicator();
-    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
-    if(this.iefix) Element.hide(this.iefix);
-  },
-
-  startIndicator: function() {
-    if(this.options.indicator) Element.show(this.options.indicator);
-  },
-
-  stopIndicator: function() {
-    if(this.options.indicator) Element.hide(this.options.indicator);
-  },
-
-  onKeyPress: function(event) {
-    if(this.active)
-      switch(event.keyCode) {
-       case Event.KEY_TAB:
-       case Event.KEY_RETURN:
-         this.selectEntry();
-         Event.stop(event);
-       case Event.KEY_ESC:
-         this.hide();
-         this.active = false;
-         Event.stop(event);
-         return;
-       case Event.KEY_LEFT:
-       case Event.KEY_RIGHT:
-         return;
-       case Event.KEY_UP:
-         this.markPrevious();
-         this.render();
-         Event.stop(event);
-         return;
-       case Event.KEY_DOWN:
-         this.markNext();
-         this.render();
-         Event.stop(event);
-         return;
-      }
-     else 
-       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
-         (Prototype.Browser.WebKit &gt; 0 &amp;&amp; event.keyCode == 0)) return;
-
-    this.changed = true;
-    this.hasFocus = true;
-
-    if(this.observer) clearTimeout(this.observer);
-      this.observer = 
-        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
-  },
-
-  activate: function() {
-    this.changed = false;
-    this.hasFocus = true;
-    this.getUpdatedChoices();
-  },
-
-  onHover: function(event) {
-    var element = Event.findElement(event, 'LI');
-    if(this.index != element.autocompleteIndex) 
-    {
-        this.index = element.autocompleteIndex;
-        this.render();
-    }
-    Event.stop(event);
-  },
-  
-  onClick: function(event) {
-    var element = Event.findElement(event, 'LI');
-    this.index = element.autocompleteIndex;
-    this.selectEntry();
-    this.hide();
-  },
-  
-  onBlur: function(event) {
-    // needed to make click events working
-    setTimeout(this.hide.bind(this), 250);
-    this.hasFocus = false;
-    this.active = false;     
-  }, 
-  
-  render: function() {
-    if(this.entryCount &gt; 0) {
-      for (var i = 0; i &lt; this.entryCount; i++)
-        this.index==i ? 
-          Element.addClassName(this.getEntry(i),&quot;selected&quot;) : 
-          Element.removeClassName(this.getEntry(i),&quot;selected&quot;);
-      if(this.hasFocus) { 
-        this.show();
-        this.active = true;
-      }
-    } else {
-      this.active = false;
-      this.hide();
-    }
-  },
-  
-  markPrevious: function() {
-    if(this.index &gt; 0) this.index--
-      else this.index = this.entryCount-1;
-    this.getEntry(this.index).scrollIntoView(true);
-  },
-  
-  markNext: function() {
-    if(this.index &lt; this.entryCount-1) this.index++
-      else this.index = 0;
-    this.getEntry(this.index).scrollIntoView(false);
-  },
-  
-  getEntry: function(index) {
-    return this.update.firstChild.childNodes[index];
-  },
-  
-  getCurrentEntry: function() {
-    return this.getEntry(this.index);
-  },
-  
-  selectEntry: function() {
-    this.active = false;
-    this.updateElement(this.getCurrentEntry());
-  },
-
-  updateElement: function(selectedElement) {
-    if (this.options.updateElement) {
-      this.options.updateElement(selectedElement);
-      return;
-    }
-    var value = '';
-    if (this.options.select) {
-      var nodes = $(selectedElement).select('.' + this.options.select) || [];
-      if(nodes.length&gt;0) value = Element.collectTextNodes(nodes[0], this.options.select);
-    } else
-      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
-    
-    var bounds = this.getTokenBounds();
-    if (bounds[0] != -1) {
-      var newValue = this.element.value.substr(0, bounds[0]);
-      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
-      if (whitespace)
-        newValue += whitespace[0];
-      this.element.value = newValue + value + this.element.value.substr(bounds[1]);
-    } else {
-      this.element.value = value;
-    }
-    this.oldElementValue = this.element.value;
-    this.element.focus();
-    
-    if (this.options.afterUpdateElement)
-      this.options.afterUpdateElement(this.element, selectedElement);
-  },
-
-  updateChoices: function(choices) {
-    if(!this.changed &amp;&amp; this.hasFocus) {
-      this.update.innerHTML = choices;
-      Element.cleanWhitespace(this.update);
-      Element.cleanWhitespace(this.update.down());
-
-      if(this.update.firstChild &amp;&amp; this.update.down().childNodes) {
-        this.entryCount = 
-          this.update.down().childNodes.length;
-        for (var i = 0; i &lt; this.entryCount; i++) {
-          var entry = this.getEntry(i);
-          entry.autocompleteIndex = i;
-          this.addObservers(entry);
-        }
-      } else { 
-        this.entryCount = 0;
-      }
-
-      this.stopIndicator();
-      this.index = 0;
-      
-      if(this.entryCount==1 &amp;&amp; this.options.autoSelect) {
-        this.selectEntry();
-        this.hide();
-      } else {
-        this.render();
-      }
-    }
-  },
-
-  addObservers: function(element) {
-    Event.observe(element, &quot;mouseover&quot;, this.onHover.bindAsEventListener(this));
-    Event.observe(element, &quot;click&quot;, this.onClick.bindAsEventListener(this));
-  },
-
-  onObserverEvent: function() {
-    this.changed = false;   
-    this.tokenBounds = null;
-    if(this.getToken().length&gt;=this.options.minChars) {
-      this.getUpdatedChoices();
-    } else {
-      this.active = false;
-      this.hide();
-    }
-    this.oldElementValue = this.element.value;
-  },
-
-  getToken: function() {
-    var bounds = this.getTokenBounds();
-    return this.element.value.substring(bounds[0], bounds[1]).strip();
-  },
-
-  getTokenBounds: function() {
-    if (null != this.tokenBounds) return this.tokenBounds;
-    var value = this.element.value;
-    if (value.strip().empty()) return [-1, 0];
-    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
-    var offset = (diff == this.oldElementValue.length ? 1 : 0);
-    var prevTokenPos = -1, nextTokenPos = value.length;
-    var tp;
-    for (var index = 0, l = this.options.tokens.length; index &lt; l; ++index) {
-      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
-      if (tp &gt; prevTokenPos) prevTokenPos = tp;
-      tp = value.indexOf(this.options.tokens[index], diff + offset);
-      if (-1 != tp &amp;&amp; tp &lt; nextTokenPos) nextTokenPos = tp;
-    }
-    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
-  }
-});
-
-Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
-  var boundary = Math.min(newS.length, oldS.length);
-  for (var index = 0; index &lt; boundary; ++index)
-    if (newS[index] != oldS[index])
-      return index;
-  return boundary;
-};
-
-Ajax.Autocompleter = Class.create(Autocompleter.Base, {
-  initialize: function(element, update, url, options) {
-    this.baseInitialize(element, update, options);
-    this.options.asynchronous  = true;
-    this.options.onComplete    = this.onComplete.bind(this);
-    this.options.defaultParams = this.options.parameters || null;
-    this.url                   = url;
-  },
-
-  getUpdatedChoices: function() {
-    this.startIndicator();
-    
-    var entry = encodeURIComponent(this.options.paramName) + '=' + 
-      encodeURIComponent(this.getToken());
-
-    this.options.parameters = this.options.callback ?
-      this.options.callback(this.element, entry) : entry;
-
-    if(this.options.defaultParams) 
-      this.options.parameters += '&amp;' + this.options.defaultParams;
-    
-    new Ajax.Request(this.url, this.options);
-  },
-
-  onComplete: function(request) {
-    this.updateChoices(request.responseText);
-  }
-});
-
-// The local array autocompleter. Used when you'd prefer to
-// inject an array of autocompletion options into the page, rather
-// than sending out Ajax queries, which can be quite slow sometimes.
-//
-// The constructor takes four parameters. The first two are, as usual,
-// the id of the monitored textbox, and id of the autocompletion menu.
-// The third is the array you want to autocomplete from, and the fourth
-// is the options block.
-//
-// Extra local autocompletion options:
-// - choices - How many autocompletion choices to offer
-//
-// - partialSearch - If false, the autocompleter will match entered
-//                    text only at the beginning of strings in the 
-//                    autocomplete array. Defaults to true, which will
-//                    match text at the beginning of any *word* in the
-//                    strings in the autocomplete array. If you want to
-//                    search anywhere in the string, additionally set
-//                    the option fullSearch to true (default: off).
-//
-// - fullSsearch - Search anywhere in autocomplete array strings.
-//
-// - partialChars - How many characters to enter before triggering
-//                   a partial match (unlike minChars, which defines
-//                   how many characters are required to do any match
-//                   at all). Defaults to 2.
-//
-// - ignoreCase - Whether to ignore case when autocompleting.
-//                 Defaults to true.
-//
-// It's possible to pass in a custom function as the 'selector' 
-// option, if you prefer to write your own autocompletion logic.
-// In that case, the other options above will not apply unless
-// you support them.
-
-Autocompleter.Local = Class.create(Autocompleter.Base, {
-  initialize: function(element, update, array, options) {
-    this.baseInitialize(element, update, options);
-    this.options.array = array;
-  },
-
-  getUpdatedChoices: function() {
-    this.updateChoices(this.options.selector(this));
-  },
-
-  setOptions: function(options) {
-    this.options = Object.extend({
-      choices: 10,
-      partialSearch: true,
-      partialChars: 2,
-      ignoreCase: true,
-      fullSearch: false,
-      selector: function(instance) {
-        var ret       = []; // Beginning matches
-        var partial   = []; // Inside matches
-        var entry     = instance.getToken();
-        var count     = 0;
-
-        for (var i = 0; i &lt; instance.options.array.length &amp;&amp;  
-          ret.length &lt; instance.options.choices ; i++) { 
-
-          var elem = instance.options.array[i];
-          var foundPos = instance.options.ignoreCase ? 
-            elem.toLowerCase().indexOf(entry.toLowerCase()) : 
-            elem.indexOf(entry);
-
-          while (foundPos != -1) {
-            if (foundPos == 0 &amp;&amp; elem.length != entry.length) { 
-              ret.push(&quot;&lt;li&gt;&lt;strong&gt;&quot; + elem.substr(0, entry.length) + &quot;&lt;/strong&gt;&quot; + 
-                elem.substr(entry.length) + &quot;&lt;/li&gt;&quot;);
-              break;
-            } else if (entry.length &gt;= instance.options.partialChars &amp;&amp; 
-              instance.options.partialSearch &amp;&amp; foundPos != -1) {
-              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
-                partial.push(&quot;&lt;li&gt;&quot; + elem.substr(0, foundPos) + &quot;&lt;strong&gt;&quot; +
-                  elem.substr(foundPos, entry.length) + &quot;&lt;/strong&gt;&quot; + elem.substr(
-                  foundPos + entry.length) + &quot;&lt;/li&gt;&quot;);
-                break;
-              }
-            }
-
-            foundPos = instance.options.ignoreCase ? 
-              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
-              elem.indexOf(entry, foundPos + 1);
-
-          }
-        }
-        if (partial.length)
-          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
-        return &quot;&lt;ul&gt;&quot; + ret.join('') + &quot;&lt;/ul&gt;&quot;;
-      }
-    }, options || { });
-  }
-});
-
-// AJAX in-place editor and collection editor
-// Full rewrite by Christophe Porteneuve &lt;tdd@tddsworld.com&gt; (April 2007).
-
-// Use this if you notice weird scrolling problems on some browsers,
-// the DOM might be a bit confused when this gets called so do this
-// waits 1 ms (with setTimeout) until it does the activation
-Field.scrollFreeActivate = function(field) {
-  setTimeout(function() {
-    Field.activate(field);
-  }, 1);
-}
-
-Ajax.InPlaceEditor = Class.create({
-  initialize: function(element, url, options) {
-    this.url = url;
-    this.element = element = $(element);
-    this.prepareOptions();
-    this._controls = { };
-    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
-    Object.extend(this.options, options || { });
-    if (!this.options.formId &amp;&amp; this.element.id) {
-      this.options.formId = this.element.id + '-inplaceeditor';
-      if ($(this.options.formId))
-        this.options.formId = '';
-    }
-    if (this.options.externalControl)
-      this.options.externalControl = $(this.options.externalControl);
-    if (!this.options.externalControl)
-      this.options.externalControlOnly = false;
-    this._originalBackground = this.element.getStyle('background-color') || 'transparent';
-    this.element.title = this.options.clickToEditText;
-    this._boundCancelHandler = this.handleFormCancellation.bind(this);
-    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
-    this._boundFailureHandler = this.handleAJAXFailure.bind(this);
-    this._boundSubmitHandler = this.handleFormSubmission.bind(this);
-    this._boundWrapperHandler = this.wrapUp.bind(this);
-    this.registerListeners();
-  },
-  checkForEscapeOrReturn: function(e) {
-    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
-    if (Event.KEY_ESC == e.keyCode)
-      this.handleFormCancellation(e);
-    else if (Event.KEY_RETURN == e.keyCode)
-      this.handleFormSubmission(e);
-  },
-  createControl: function(mode, handler, extraClasses) {
-    var control = this.options[mode + 'Control'];
-    var text = this.options[mode + 'Text'];
-    if ('button' == control) {
-      var btn = document.createElement('input');
-      btn.type = 'submit';
-      btn.value = text;
-      btn.className = 'editor_' + mode + '_button';
-      if ('cancel' == mode)
-        btn.onclick = this._boundCancelHandler;
-      this._form.appendChild(btn);
-      this._controls[mode] = btn;
-    } else if ('link' == control) {
-      var link = document.createElement('a');
-      link.href = '#';
-      link.appendChild(document.createTextNode(text));
-      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
-      link.className = 'editor_' + mode + '_link';
-      if (extraClasses)
-        link.className += ' ' + extraClasses;
-      this._form.appendChild(link);
-      this._controls[mode] = link;
-    }
-  },
-  createEditField: function() {
-    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
-    var fld;
-    if (1 &gt;= this.options.rows &amp;&amp; !/\r|\n/.test(this.getText())) {
-      fld = document.createElement('input');
-      fld.type = 'text';
-      var size = this.options.size || this.options.cols || 0;
-      if (0 &lt; size) fld.size = size;
-    } else {
-      fld = document.createElement('textarea');
-      fld.rows = (1 &gt;= this.options.rows ? this.options.autoRows : this.options.rows);
-      fld.cols = this.options.cols || 40;
-    }
-    fld.name = this.options.paramName;
-    fld.value = text; // No HTML breaks conversion anymore
-    fld.className = 'editor_field';
-    if (this.options.submitOnBlur)
-      fld.onblur = this._boundSubmitHandler;
-    this._controls.editor = fld;
-    if (this.options.loadTextURL)
-      this.loadExternalText();
-    this._form.appendChild(this._controls.editor);
-  },
-  createForm: function() {
-    var ipe = this;
-    function addText(mode, condition) {
-      var text = ipe.options['text' + mode + 'Controls'];
-      if (!text || condition === false) return;
-      ipe._form.appendChild(document.createTextNode(text));
-    };
-    this._form = $(document.createElement('form'));
-    this._form.id = this.options.formId;
-    this._form.addClassName(this.options.formClassName);
-    this._form.onsubmit = this._boundSubmitHandler;
-    this.createEditField();
-    if ('textarea' == this._controls.editor.tagName.toLowerCase())
-      this._form.appendChild(document.createElement('br'));
-    if (this.options.onFormCustomization)
-      this.options.onFormCustomization(this, this._form);
-    addText('Before', this.options.okControl || this.options.cancelControl);
-    this.createControl('ok', this._boundSubmitHandler);
-    addText('Between', this.options.okControl &amp;&amp; this.options.cancelControl);
-    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
-    addText('After', this.options.okControl || this.options.cancelControl);
-  },
-  destroy: function() {
-    if (this._oldInnerHTML)
-      this.element.innerHTML = this._oldInnerHTML;
-    this.leaveEditMode();
-    this.unregisterListeners();
-  },
-  enterEditMode: function(e) {
-    if (this._saving || this._editing) return;
-    this._editing = true;
-    this.triggerCallback('onEnterEditMode');
-    if (this.options.externalControl)
-      this.options.externalControl.hide();
-    this.element.hide();
-    this.createForm();
-    this.element.parentNode.insertBefore(this._form, this.element);
-    if (!this.options.loadTextURL)
-      this.postProcessEditField();
-    if (e) Event.stop(e);
-  },
-  enterHover: function(e) {
-    if (this.options.hoverClassName)
-      this.element.addClassName(this.options.hoverClassName);
-    if (this._saving) return;
-    this.triggerCallback('onEnterHover');
-  },
-  getText: function() {
-    return this.element.innerHTML;
-  },
-  handleAJAXFailure: function(transport) {
-    this.triggerCallback('onFailure', transport);
-    if (this._oldInnerHTML) {
-      this.element.innerHTML = this._oldInnerHTML;
-      this._oldInnerHTML = null;
-    }
-  },
-  handleFormCancellation: function(e) {
-    this.wrapUp();
-    if (e) Event.stop(e);
-  },
-  handleFormSubmission: function(e) {
-    var form = this._form;
-    var value = $F(this._controls.editor);
-    this.prepareSubmission();
-    var params = this.options.callback(form, value) || '';
-    if (Object.isString(params))
-      params = params.toQueryParams();
-    params.editorId = this.element.id;
-    if (this.options.htmlResponse) {
-      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
-      Object.extend(options, {
-        parameters: params,
-        onComplete: this._boundWrapperHandler,
-        onFailure: this._boundFailureHandler
-      });
-      new Ajax.Updater({ success: this.element }, this.url, options);
-    } else {
-      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
-      Object.extend(options, {
-        parameters: params,
-        onComplete: this._boundWrapperHandler,
-        onFailure: this._boundFailureHandler
-      });
-      new Ajax.Request(this.url, options);
-    }
-    if (e) Event.stop(e);
-  },
-  leaveEditMode: function() {
-    this.element.removeClassName(this.options.savingClassName);
-    this.removeForm();
-    this.leaveHover();
-    this.element.style.backgroundColor = this._originalBackground;
-    this.element.show();
-    if (this.options.externalControl)
-      this.options.externalControl.show();
-    this._saving = false;
-    this._editing = false;
-    this._oldInnerHTML = null;
-    this.triggerCallback('onLeaveEditMode');
-  },
-  leaveHover: function(e) {
-    if (this.options.hoverClassName)
-      this.element.removeClassName(this.options.hoverClassName);
-    if (this._saving) return;
-    this.triggerCallback('onLeaveHover');
-  },
-  loadExternalText: function() {
-    this._form.addClassName(this.options.loadingClassName);
-    this._controls.editor.disabled = true;
-    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
-    Object.extend(options, {
-      parameters: 'editorId=' + encodeURIComponent(this.element.id),
-      onComplete: Prototype.emptyFunction,
-      onSuccess: function(transport) {
-        this._form.removeClassName(this.options.loadingClassName);
-        var text = transport.responseText;
-        if (this.options.stripLoadedTextTags)
-          text = text.stripTags();
-        this._controls.editor.value = text;
-        this._controls.editor.disabled = false;
-        this.postProcessEditField();
-      }.bind(this),
-      onFailure: this._boundFailureHandler
-    });
-    new Ajax.Request(this.options.loadTextURL, options);
-  },
-  postProcessEditField: function() {
-    var fpc = this.options.fieldPostCreation;
-    if (fpc)
-      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
-  },
-  prepareOptions: function() {
-    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
-    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
-    [this._extraDefaultOptions].flatten().compact().each(function(defs) {
-      Object.extend(this.options, defs);
-    }.bind(this));
-  },
-  prepareSubmission: function() {
-    this._saving = true;
-    this.removeForm();
-    this.leaveHover();
-    this.showSaving();
-  },
-  registerListeners: function() {
-    this._listeners = { };
-    var listener;
-    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
-      listener = this[pair.value].bind(this);
-      this._listeners[pair.key] = listener;
-      if (!this.options.externalControlOnly)
-        this.element.observe(pair.key, listener);
-      if (this.options.externalControl)
-        this.options.externalControl.observe(pair.key, listener);
-    }.bind(this));
-  },
-  removeForm: function() {
-    if (!this._form) return;
-    this._form.remove();
-    this._form = null;
-    this._controls = { };
-  },
-  showSaving: function() {
-    this._oldInnerHTML = this.element.innerHTML;
-    this.element.innerHTML = this.options.savingText;
-    this.element.addClassName(this.options.savingClassName);
-    this.element.style.backgroundColor = this._originalBackground;
-    this.element.show();
-  },
-  triggerCallback: function(cbName, arg) {
-    if ('function' == typeof this.options[cbName]) {
-      this.options[cbName](this, arg);
-    }
-  },
-  unregisterListeners: function() {
-    $H(this._listeners).each(function(pair) {
-      if (!this.options.externalControlOnly)
-        this.element.stopObserving(pair.key, pair.value);
-      if (this.options.externalControl)
-        this.options.externalControl.stopObserving(pair.key, pair.value);
-    }.bind(this));
-  },
-  wrapUp: function(transport) {
-    this.leaveEditMode();
-    // Can't use triggerCallback due to backward compatibility: requires
-    // binding + direct element
-    this._boundComplete(transport, this.element);
-  }
-});
-
-Object.extend(Ajax.InPlaceEditor.prototype, {
-  dispose: Ajax.InPlaceEditor.prototype.destroy
-});
-
-Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
-  initialize: function($super, element, url, options) {
-    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
-    $super(element, url, options);
-  },
-
-  createEditField: function() {
-    var list = document.createElement('select');
-    list.name = this.options.paramName;
-    list.size = 1;
-    this._controls.editor = list;
-    this._collection = this.options.collection || [];
-    if (this.options.loadCollectionURL)
-      this.loadCollection();
-    else
-      this.checkForExternalText();
-    this._form.appendChild(this._controls.editor);
-  },
-
-  loadCollection: function() {
-    this._form.addClassName(this.options.loadingClassName);
-    this.showLoadingText(this.options.loadingCollectionText);
-    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
-    Object.extend(options, {
-      parameters: 'editorId=' + encodeURIComponent(this.element.id),
-      onComplete: Prototype.emptyFunction,
-      onSuccess: function(transport) {
-        var js = transport.responseText.strip();
-        if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
-          throw 'Server returned an invalid collection representation.';
-        this._collection = eval(js);
-        this.checkForExternalText();
-      }.bind(this),
-      onFailure: this.onFailure
-    });
-    new Ajax.Request(this.options.loadCollectionURL, options);
-  },
-
-  showLoadingText: function(text) {
-    this._controls.editor.disabled = true;
-    var tempOption = this._controls.editor.firstChild;
-    if (!tempOption) {
-      tempOption = document.createElement('option');
-      tempOption.value = '';
-      this._controls.editor.appendChild(tempOption);
-      tempOption.selected = true;
-    }
-    tempOption.update((text || '').stripScripts().stripTags());
-  },
-
-  checkForExternalText: function() {
-    this._text = this.getText();
-    if (this.options.loadTextURL)
-      this.loadExternalText();
-    else
-      this.buildOptionList();
-  },
-
-  loadExternalText: function() {
-    this.showLoadingText(this.options.loadingText);
-    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
-    Object.extend(options, {<