Changeset 1346 for mrblog/trunk

Show
Ignore:
Timestamp:
02/10/08 11:52:39 (8 months ago)
Author:
jon.egil.stra..@gmail.com
Message:

Simplified and robustified

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • mrblog/trunk/app/controllers/admin.rb

    r1343 r1346  
    55  def index 
    66    @pager = ::Paginator.new(Post.count, MrBlog::PER_PAGE) do |offset, per_page| 
    7            Post.get(:all, :limit => per_page, :offset => offset, :order => 'created_at DESC') 
    8          end 
    9          @page = @pager.page(params[:page]) 
    10          render :action => "index"  
     7      Post.get(:all, :limit => per_page, :offset => offset, :order => 'created_at DESC') 
     8    end 
     9    @page = @pager.page(params[:page]) 
     10    render :action => "index"  
    1111  end 
    1212   
     
    1717   
    1818  def update 
    19     if @post = Post.find(params[:post][:id]) 
     19    if @post = Post.find(params[:id]) 
    2020      @post.update_attributes(params[:post]) 
    2121      redirect "/admin/show/#{@post.id}"  
     
    2626     
    2727  def show 
    28     @post = Post.find params[:id] 
     28    @post = Post.find(params[:id]) 
    2929    @comments = @post.comments 
    3030    render 
     
    4949 
    5050  def delete_comment 
    51     Comment.find(params[:id]).destroy  
    52     redirect "/admin" 
     51    c = Comment.find(params[:id]) 
     52    post = c.post 
     53    c.destroy     
     54    redirect "/admin/show/#{post.id}" 
    5355  end 
    5456end  
  • mrblog/trunk/app/controllers/blog.rb

    r1343 r1346  
    22 
    33  def show 
    4     @post = Post.find_by_id params[:id] 
     4    @post = Post.find_by_id(params[:id]) 
    55    if @post.nil? 
    66      redirect_back_or_default("/") 
    77    else 
    88      @comments = @post.comments 
     9      @comment = @comments.new 
     10      @user = @post.user 
    911      render 
    1012    end 
     
    1416    @pager = ::Paginator.new(Post.count, MrBlog::PER_PAGE) do |offset, per_page| 
    1517      Post.find(:all, :limit => per_page, :offset => offset) 
    16          end 
    17          @page = @pager.page(params[:page]) 
    18          render :action => "index"     
     18    end 
     19    @page = @pager.page(params[:page]) 
     20    render :action => "index"     
    1921  end 
    2022   
    2123  def add_comment 
    22     @post = Post.find params[:post_id] 
    23     @post.comments.create(:name => params[:comment_name], 
    24                           :text => params[:comment_text]) 
    25     @comments = @post.comments.reload 
    26     render_js 'comments' 
     24    @post = Post.find(params[:id])     
     25    @post.comments.create(params[:comment]) 
     26    redirect "/blog/show/#{@post.id}"     
    2727  end 
    28    
    29   def delete_comment 
    30     @post = Post.find params[:post_id] 
    31     @post.comments.destroy params[:id] 
    32     @comments = @post.comments.reload 
    33     render_js 'comments' 
    34   end 
    35  
    3628end 
  • mrblog/trunk/app/models/post.rb

    r1343 r1346  
    22  belongs_to :user 
    33  has_many :comments 
     4 
     5  validates_presence_of :user 
    46 
    57  def self.get(how_many = :all, options = {}) 
  • mrblog/trunk/app/views/admin/show.html.erb

    r1343 r1346  
    33<h2><%= @post.title %></h2> 
    44<div class="maintext"> 
    5 <%= @post.text%>         
     5  <%= @post.text%>   
    66</div> 
     7 
    78<% @comments.each do |c| %> 
    89  <div> 
     
    1718<% end %> 
    1819 
    19  
    2020<%= link_to image_tag('edit.gif'), "/admin/edit/#{@post.id}", :class => "button" %> 
  • mrblog/trunk/app/views/blog/_comments.html.erb

    r1343 r1346  
    11<% @comments.each do |c| %> 
    2   <div id='<%= c.id %>'> 
    3     <p><%= c.name %>: <%= c.text %></p> 
     2  <div> 
     3    <p> 
     4      <b><%= c.name %></b>, on <%= c.created_at.strftime("%B %d, %Y") %> said: <br /> 
     5      <%= c.text %> <br /> 
     6    </p> 
    47  </div> 
    58<% end %> 
  • mrblog/trunk/app/views/blog/show.html.erb

    r1343 r1346  
    11<h1><%= @post.title %></h1> 
    2 <div><%= @post.text%></div> 
    3 <hr />   
     2<div> 
     3  <%= @post.text.gsub("\n", "<br />") %> 
     4  <br /> 
     5  <b><%= @user.login %></b>, on <%= @post.created_at.strftime("%B %d, %Y") %> 
     6  <%= @post.updated_at > @post.created_at ? "(edited on #{@post.updated_at.strftime('%B %d, %Y')})" : "" %> 
     7</div> 
     8<hr />   
    49<p>Comments:<%= " None yet" if @comments.empty? %></p> 
    510<% if !@comments.empty? %> 
    611  <div id='comments'> 
    7     <%= partial(:comments) %>   
     12    <%= partial(:comments) %>  
    813  </div> 
    914<% end %> 
    10 <div id="comment-form"> 
    11   <form class="mascot" onsubmit="new Ajax.Request('/blog/add_comment?post_id=<%= @post.id %>', 
    12                   {asynchronous:'true',  
    13                   evalScripts:'true',  
    14                   parameters: Form.serialize(this)});  
    15                   return false;" method="post" action="/posts/add_comment" id='c-form'> 
    16  
     15<div id="comment-form">  
     16  <% form_for :comment, :action => url(:controller => "blog",  
     17                                       :action => "add_comment",  
     18                                       :id => @post.id) do %> 
    1719    <p> 
    18       <label for="comment_name">Name</label> 
    19       <input type="text" size="20" name="comment_name" id="comment_name" /> 
     20      <%= text_control :name, :label => 'Name', :cols => 50 %> 
    2021    </p> 
    2122    <p> 
    22       <label for="comment_text">Comment</label> 
    23       <textarea cols="20" id="comment_text" name="comment_text" rows="8"></textarea> 
     23      <%= text_area_control :text, :label => 'Text', :rows => 20, :cols => 50 %> 
    2424    </p> 
    25  
    26     <p class="buttons"> 
    27       <button name="commit" type="submit" value="Submit" class="imaged">Submit</button> 
     25    <p class='button'> 
     26      <%= submit_button(image_tag('save.gif')) %>     
    2827    </p> 
    29   </form> 
    30   <script type='text/javascript'> 
    31     Form.reset('c-form'); 
    32   </script> 
     28  <% end %> 
    3329</div> 
  • mrblog/trunk/public/javascripts/prototype.js

    r1294 r1346  
    1 /*  Prototype JavaScript framework, version 1.5.0_rc1 
    2  *  (c) 2005 Sam Stephenson <sam@conio.net> 
     1/*  Prototype JavaScript framework, version 1.6.0.2 
     2 *  (c) 2005-2008 Sam Stephenson 
    33 * 
    44 *  Prototype is freely distributable under the terms of an MIT-style license. 
    5  *  For details, see the Prototype web site: http://prototype.conio.net
     5 *  For details, see the Prototype web site: http://www.prototypejs.org
    66 * 
    7 /*--------------------------------------------------------------------------*/ 
     7 *--------------------------------------------------------------------------*/ 
    88 
    99var Prototype = { 
    10   Version: '1.5.0_rc1', 
     10  Version: '1.6.0.2', 
     11 
     12  Browser: { 
     13    IE:     !!(window.attachEvent && !window.opera), 
     14    Opera:  !!window.opera, 
     15    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, 
     16    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, 
     17    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) 
     18  }, 
     19 
    1120  BrowserFeatures: { 
    12     XPath: !!document.evaluate 
    13   }, 
    14  
    15   ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)', 
    16   emptyFunction: function() {}, 
     21    XPath: !!document.evaluate, 
     22    ElementExtensions: !!window.HTMLElement, 
     23    SpecificElementExtensions: 
     24      document.createElement('div').__proto__ && 
     25      document.createElement('div').__proto__ !== 
     26        document.createElement('form').__proto__ 
     27  }, 
     28 
     29  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>', 
     30  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, 
     31 
     32  emptyFunction: function() { }, 
    1733  K: function(x) { return x } 
    18 
    19  
     34}; 
     35 
     36if (Prototype.Browser.MobileSafari) 
     37  Prototype.BrowserFeatures.SpecificElementExtensions = false; 
     38 
     39 
     40/* Based on Alex Arnell's inheritance implementation. */ 
    2041var Class = { 
    2142  create: function() { 
    22     return function() { 
     43    var parent = null, properties = $A(arguments); 
     44    if (Object.isFunction(properties[0])) 
     45      parent = properties.shift(); 
     46 
     47    function klass() { 
    2348      this.initialize.apply(this, arguments); 
    2449    } 
    25   } 
    26 
    27  
    28 var Abstract = new Object(); 
     50 
     51    Object.extend(klass, Class.Methods); 
     52    klass.superclass = parent; 
     53    klass.subclasses = []; 
     54 
     55    if (parent) { 
     56      var subclass = function() { }; 
     57      subclass.prototype = parent.prototype; 
     58      klass.prototype = new subclass; 
     59      parent.subclasses.push(klass); 
     60    } 
     61 
     62    for (var i = 0; i < properties.length; i++) 
     63      klass.addMethods(properties[i]); 
     64 
     65    if (!klass.prototype.initialize) 
     66      klass.prototype.initialize = Prototype.emptyFunction; 
     67 
     68    klass.prototype.constructor = klass; 
     69 
     70    return klass; 
     71  } 
     72}; 
     73 
     74Class.Methods = { 
     75  addMethods: function(source) { 
     76    var ancestor   = this.superclass && this.superclass.prototype; 
     77    var properties = Object.keys(source); 
     78 
     79    if (!Object.keys({ toString: true }).length) 
     80      properties.push("toString", "valueOf"); 
     81 
     82    for (var i = 0, length = properties.length; i < length; i++) { 
     83      var property = properties[i], value = source[property]; 
     84      if (ancestor && Object.isFunction(value) && 
     85          value.argumentNames().first() == "$super") { 
     86        var method = value, value = Object.extend((function(m) { 
     87          return function() { return ancestor[m].apply(this, arguments) }; 
     88        })(property).wrap(method), { 
     89          valueOf:  function() { return method }, 
     90          toString: function() { return method.toString() } 
     91        }); 
     92      } 
     93      this.prototype[property] = value; 
     94    } 
     95 
     96    return this; 
     97  } 
     98}; 
     99 
     100var Abstract = { }; 
    29101 
    30102Object.extend = function(destination, source) { 
    31   for (var property in source) { 
     103  for (var property in source) 
    32104    destination[property] = source[property]; 
    33   } 
    34105  return destination; 
    35 } 
     106}; 
    36107 
    37108Object.extend(Object, { 
    38109  inspect: function(object) { 
    39110    try { 
    40       if (object == undefined) return 'undefined'; 
    41       if (object == null) return 'null'; 
    42       return object.inspect ? object.inspect() : object.toString(); 
     111      if (Object.isUndefined(object)) return 'undefined'; 
     112      if (object === null) return 'null'; 
     113      return object.inspect ? object.inspect() : String(object); 
    43114    } catch (e) { 
    44115      if (e instanceof RangeError) return '...'; 
    45116      throw e; 
    46117    } 
     118  }, 
     119 
     120  toJSON: function(object) { 
     121    var type = typeof object; 
     122    switch (type) { 
     123      case 'undefined': 
     124      case 'function': 
     125      case 'unknown': return; 
     126      case 'boolean': return object.toString(); 
     127    } 
     128 
     129    if (object === null) return 'null'; 
     130    if (object.toJSON) return object.toJSON(); 
     131    if (Object.isElement(object)) return; 
     132 
     133    var results = []; 
     134    for (var property in object) { 
     135      var value = Object.toJSON(object[property]); 
     136      if (!Object.isUndefined(value)) 
     137        results.push(property.toJSON() + ': ' + value); 
     138    } 
     139 
     140    return '{' + results.join(', ') + '}'; 
     141  }, 
     142 
     143  toQueryString: function(object) { 
     144    return $H(object).toQueryString(); 
     145  }, 
     146 
     147  toHTML: function(object) { 
     148    return object && object.toHTML ? object.toHTML() : String.interpret(object); 
    47149  }, 
    48150 
     
    62164 
    63165  clone: function(object) { 
    64     return Object.extend({}, object); 
     166    return Object.extend({ }, object); 
     167  }, 
     168 
     169  isElement: function(object) { 
     170    return object && object.nodeType == 1; 
     171  }, 
     172 
     173  isArray: function(object) { 
     174    return object != null && typeof object == "object" && 
     175      'splice' in object && 'join' in object; 
     176  }, 
     177 
     178  isHash: function(object) { 
     179    return object instanceof Hash; 
     180  }, 
     181 
     182  isFunction: function(object) { 
     183    return typeof object == "function"; 
     184  }, 
     185 
     186  isString: function(object) { 
     187    return typeof object == "string"; 
     188  }, 
     189 
     190  isNumber: function(object) { 
     191    return typeof object == "number"; 
     192  }, 
     193 
     194  isUndefined: function(object) { 
     195    return typeof object == "undefined"; 
    65196  } 
    66197}); 
    67198 
    68 Function.prototype.bind = function() { 
    69   var __method = this, args = $A(arguments), object = args.shift(); 
    70   return function() { 
    71     return __method.apply(object, args.concat($A(arguments))); 
    72   } 
    73 
    74  
    75 Function.prototype.bindAsEventListener = function(object) { 
    76   var __method = this, args = $A(arguments), object = args.shift(); 
    77   return function(event) { 
    78     return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments))); 
    79   } 
    80 
    81  
    82 Object.extend(Number.prototype, { 
    83   toColorPart: function() { 
    84     var digits = this.toString(16); 
    85     if (this < 16) return '0' + digits; 
    86     return digits; 
    87   }, 
    88  
    89   succ: function() { 
    90     return this + 1; 
    91   }, 
    92  
    93   times: function(iterator) { 
    94     $R(0, this, true).each(iterator); 
    95     return this; 
     199Object.extend(Function.prototype, { 
     200  argumentNames: function() { 
     201    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); 
     202    return names.length == 1 && !names[0] ? [] : names; 
     203  }, 
     204 
     205  bind: function() { 
     206    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; 
     207    var __method = this, args = $A(arguments), object = args.shift(); 
     208    return function() { 
     209      return __method.apply(object, args.concat($A(arguments))); 
     210    } 
     211  }, 
     212 
     213  bindAsEventListener: function() { 
     214    var __method = this, args = $A(arguments), object = args.shift(); 
     215    return function(event) { 
     216      return __method.apply(object, [event || window.event].concat(args)); 
     217    } 
     218  }, 
     219 
     220  curry: function() { 
     221    if (!arguments.length) return this; 
     222    var __method = this, args = $A(arguments); 
     223    return function() { 
     224      return __method.apply(this, args.concat($A(arguments))); 
     225    } 
     226  }, 
     227 
     228  delay: function() { 
     229    var __method = this, args = $A(arguments), timeout = args.shift() * 1000; 
     230    return window.setTimeout(function() { 
     231      return __method.apply(__method, args); 
     232    }, timeout); 
     233  }, 
     234 
     235  wrap: function(wrapper) { 
     236    var __method = this; 
     237    return function() { 
     238      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); 
     239    } 
     240  }, 
     241 
     242  methodize: function() { 
     243    if (this._methodized) return this._methodized; 
     244    var __method = this; 
     245    return this._methodized = function() { 
     246      return __method.apply(null, [this].concat($A(arguments))); 
     247    }; 
    96248  } 
    97249}); 
     250 
     251Function.prototype.defer = Function.prototype.delay.curry(0.01); 
     252 
     253Date.prototype.toJSON = function() { 
     254  return '"' + this.getUTCFullYear() + '-' + 
     255    (this.getUTCMonth() + 1).toPaddedString(2) + '-' + 
     256    this.getUTCDate().toPaddedString(2) + 'T' + 
     257    this.getUTCHours().toPaddedString(2) + ':' + 
     258    this.getUTCMinutes().toPaddedString(2) + ':' + 
     259    this.getUTCSeconds().toPaddedString(2) + 'Z"'; 
     260}; 
    98261 
    99262var Try = { 
     
    101264    var returnValue; 
    102265 
    103     for (var i = 0; i < arguments.length; i++) { 
     266    for (var i = 0, length = arguments.length; i < length; i++) { 
    104267      var lambda = arguments[i]; 
    105268      try { 
    106269        returnValue = lambda(); 
    107270        break; 
    108       } catch (e) {
     271      } catch (e) {
    109272    } 
    110273 
    111274    return returnValue; 
    112275  } 
    113 
     276}; 
     277 
     278RegExp.prototype.match = RegExp.prototype.test; 
     279 
     280RegExp.escape = function(str) { 
     281  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); 
     282}; 
    114283 
    115284/*--------------------------------------------------------------------------*/ 
    116285 
    117 var PeriodicalExecuter = Class.create(); 
    118 PeriodicalExecuter.prototype = { 
     286var PeriodicalExecuter = Class.create({ 
    119287  initialize: function(callback, frequency) { 
    120288    this.callback = callback; 
     
    129297  }, 
    130298 
     299  execute: function() { 
     300    this.callback(this); 
     301  }, 
     302 
    131303  stop: function() { 
    132304    if (!this.timer) return; 
     
    139311      try { 
    140312        this.currentlyExecuting = true; 
    141         this.callback(this); 
     313        this.execute(); 
    142314      } finally { 
    143315        this.currentlyExecuting = false; 
     
    145317    } 
    146318  } 
    147 
     319}); 
     320Object.extend(String, { 
     321  interpret: function(value) { 
     322    return value == null ? '' : String(value); 
     323  }, 
     324  specialChar: { 
     325    '\b': '\\b', 
     326    '\t': '\\t', 
     327    '\n': '\\n', 
     328    '\f': '\\f', 
     329    '\r': '\\r', 
     330    '\\': '\\\\' 
     331  } 
     332}); 
     333 
    148334Object.extend(String.prototype, { 
    149335  gsub: function(pattern, replacement) { 
     
    154340      if (match = source.match(pattern)) { 
    155341        result += source.slice(0, match.index); 
    156         result += (replacement(match) || '').toString(); 
     342        result += String.interpret(replacement(match)); 
    157343        source  = source.slice(match.index + match[0].length); 
    158344      } else { 
     
    165351  sub: function(pattern, replacement, count) { 
    166352    replacement = this.gsub.prepareReplacement(replacement); 
    167     count = count === undefined ? 1 : count; 
     353    count = Object.isUndefined(count) ? 1 : count; 
    168354 
    169355    return this.gsub(pattern, function(match) { 
     
    175361  scan: function(pattern, iterator) { 
    176362    this.gsub(pattern, iterator); 
    177     return this
     363    return String(this)
    178364  }, 
    179365 
    180366  truncate: function(length, truncation) { 
    181367    length = length || 30; 
    182     truncation = truncation === undefined ? '...' : truncation; 
     368    truncation = Object.isUndefined(truncation) ? '...' : truncation; 
    183369    return this.length > length ? 
    184       this.slice(0, length - truncation.length) + truncation : this
     370      this.slice(0, length - truncation.length) + truncation : String(this)
    185371  }, 
    186372 
     
    210396 
    211397  escapeHTML: function() { 
    212     var div = document.createElement('div'); 
    213     var text = document.createTextNode(this); 
    214     div.appendChild(text); 
    215     return div.innerHTML; 
     398    var self = arguments.callee; 
     399    self.text.data = this; 
     400    return self.div.innerHTML; 
    216401  }, 
    217402 
    218403  unescapeHTML: function() { 
    219     var div = document.createElement('div'); 
     404    var div = new Element('div'); 
    220405    div.innerHTML = this.stripTags(); 
    221     return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; 
    222   }, 
    223  
    224   toQueryParams: function() { 
    225     var pairs = this.match(/^\??(.*)$/)[1].split('&'); 
    226     return pairs.inject({}, function(params, pairString) { 
    227       var pair  = pairString.split('='); 
    228       var value = pair[1] ? decodeURIComponent(pair[1]) : undefined; 
    229       params[decodeURIComponent(pair[0])] = value; 
    230       return params; 
     406    return div.childNodes[0] ? (div.childNodes.length > 1 ? 
     407      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : 
     408      div.childNodes[0].nodeValue) : ''; 
     409  }, 
     410 
     411  toQueryParams: function(separator) { 
     412    var match = this.strip().match(/([^?#]*)(#.*)?$/); 
     413    if (!match) return { }; 
     414 
     415    return match[1].split(separator || '&').inject({ }, function(hash, pair) { 
     416      if ((pair = pair.split('='))[0]) { 
     417        var key = decodeURIComponent(pair.shift()); 
     418        var value = pair.length > 1 ? pair.join('=') : pair[0]; 
     419        if (value != undefined) value = decodeURIComponent(value); 
     420 
     421        if (key in hash) { 
     422          if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; 
     423          hash[key].push(value); 
     424        } 
     425        else hash[key] = value; 
     426      } 
     427      return hash; 
    231428    }); 
    232429  }, 
     
    236433  }, 
    237434 
     435  succ: function() { 
     436    return this.slice(0, this.length - 1) + 
     437      String.fromCharCode(this.charCodeAt(this.length - 1) + 1); 
     438  }, 
     439 
     440  times: function(count) { 
     441    return count < 1 ? '' : new Array(count + 1).join(this); 
     442  }, 
     443 
    238444  camelize: function() { 
    239     var oStringList = this.split('-'); 
    240     if (oStringList.length == 1) return oStringList[0]; 
    241  
    242     var camelizedString = this.indexOf('-') == 0 
    243       ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) 
    244       : oStringList[0]; 
    245  
    246     for (var i = 1, len = oStringList.length; i < len; i++) { 
    247       var s = oStringList[i]; 
    248       camelizedString += s.charAt(0).toUpperCase() + s.substring(1); 
    249     } 
    250  
    251     return camelizedString; 
     445    var parts = this.split('-'), len = parts.length; 
     446    if (len == 1) return parts[0]; 
     447 
     448    var camelized = this.charAt(0) == '-' 
     449      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) 
     450      : parts[0]; 
     451 
     452    for (var i = 1; i < len; i++) 
     453      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); 
     454 
     455    return camelized; 
     456  }, 
     457 
     458  capitalize: function() { 
     459    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); 
     460  }, 
     461 
     462  underscore: function() { 
     463    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); 
     464  }, 
     465 
     466  dasherize: function() { 
     467    return this.gsub(/_/,'-'); 
    252468  }, 
    253469 
    254470  inspect: function(useDoubleQuotes) { 
    255     var escapedString = this.replace(/\\/g, '\\\\'); 
    256     if (useDoubleQuotes) 
    257       return '"' + escapedString.replace(/"/g, '\\"') + '"'; 
    258     else 
    259       return "'" + escapedString.replace(/'/g, '\\\'') + "'"; 
     471    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { 
     472      var character = String.specialChar[match[0]]; 
     473      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); 
     474    }); 
     475    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; 
     476    return "'" + escapedString.replace(/'/g, '\\\'') + "'"; 
     477  }, 
     478 
     479  toJSON: function() { 
     480    return this.inspect(true); 
     481  }, 
     482 
     483  unfilterJSON: function(filter) { 
     484    return this.sub(filter || Prototype.JSONFilter, '#{1}'); 
     485  }, 
     486 
     487  isJSON: function() { 
     488    var str = this; 
     489    if (str.blank()) return false; 
     490    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); 
     491    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); 
     492  }, 
     493 
     494  evalJSON: function(sanitize) { 
     495    var json = this.unfilterJSON(); 
     496    try { 
     497      if (!sanitize || json.isJSON()) return eval('(' + json + ')'); 
     498    } catch (e) { } 
     499    throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); 
     500  }, 
     501 
     502  include: function(pattern) { 
     503    return this.indexOf(pattern) > -1; 
     504  }, 
     505 
     506  startsWith: function(pattern) { 
     507    return this.indexOf(pattern) === 0; 
     508  }, 
     509 
     510  endsWith: function(pattern) { 
     511    var d = this.length - pattern.length; 
     512    return d >= 0 && this.lastIndexOf(pattern) === d; 
     513  }, 
     514 
     515  empty: function() { 
     516    return this == ''; 
     517  }, 
     518 
     519  blank: function() { 
     520    return /^\s*$/.test(this); 
     521  }, 
     522 
     523  interpolate: function(object, pattern) { 
     524    return new Template(this, pattern).evaluate(object); 
    260525  } 
    261526}); 
    262527 
     528if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { 
     529  escapeHTML: function() { 
     530    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); 
     531  }, 
     532  unescapeHTML: function() { 
     533    return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>'); 
     534  } 
     535}); 
     536 
    263537String.prototype.gsub.prepareReplacement = function(replacement) { 
    264   if (typeof replacement == 'function') return replacement; 
     538  if (Object.isFunction(replacement)) return replacement; 
    265539  var template = new Template(replacement); 
    266540  return function(match) { return template.evaluate(match) }; 
    267 } 
     541}; 
    268542 
    269543String.prototype.parseQuery = String.prototype.toQueryParams; 
    270544 
    271 var Template = Class.create(); 
    272 Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; 
    273 Template.prototype = { 
     545Object.extend(String.prototype.escapeHTML, { 
     546  div:  document.createElement('div'), 
     547  text: document.createTextNode('') 
     548}); 
     549 
     550with (String.prototype.escapeHTML) div.appendChild(text); 
     551 
     552var Template = Class.create({ 
    274553  initialize: function(template, pattern) { 
    275554    this.template = template.toString(); 
    276     this.pattern = pattern || Template.Pattern; 
     555    this.pattern = pattern || Template.Pattern; 
    277556  }, 
    278557 
    279558  evaluate: function(object) { 
     559    if (Object.isFunction(object.toTemplateReplacements)) 
     560      object = object.toTemplateReplacements(); 
     561 
    280562    return this.template.gsub(this.pattern, function(match) { 
    281       var before = match[1]; 
     563      if (object == null) return ''; 
     564 
     565      var before = match[1] || ''; 
    282566      if (before == '\\') return match[2]; 
    283       return before + (object[match[3]] || '').toString(); 
     567 
     568      var ctx = object, expr = match[3]; 
     569      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; 
     570      match = pattern.exec(expr); 
     571      if (match == null) return before; 
     572 
     573      while (match != null) { 
     574        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; 
     575        ctx = ctx[comp]; 
     576        if (null == ctx || '' == match[3]) break; 
     577        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); 
     578        match = pattern.exec(expr); 
     579      } 
     580 
     581      return before + String.interpret(ctx); 
    284582    }); 
    285583  } 
    286 } 
    287  
    288 var $break    = new Object(); 
    289 var $continue = new Object()
     584}); 
     585Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; 
     586 
     587var $break = { }
    290588 
    291589var Enumerable = { 
    292   each: function(iterator) { 
     590  each: function(iterator, context) { 
    293591    var index = 0; 
     592    iterator = iterator.bind(context); 
    294593    try { 
    295594      this._each(function(value) { 
    296         try { 
    297           iterator(value, index++); 
    298         } catch (e) { 
    299           if (e != $continue) throw e; 
    300         } 
     595        iterator(value, index++); 
    301596      }); 
    302597    } catch (e) { 
    303598      if (e != $break) throw e; 
    304599    } 
    305   }, 
    306  
    307   all: function(iterator) { 
     600    return this; 
     601  }, 
     602 
     603  eachSlice: function(number, iterator, context) { 
     604    iterator = iterator ? iterator.bind(context) : Prototype.K; 
     605    var index = -number, slices = [], array = this.toArray(); 
     606    while ((index += number) < array.length) 
     607      slices.push(array.slice(index, index+number)); 
     608    return slices.collect(iterator, context); 
     609  }, 
     610 
     611  all: function(iterator, context) { 
     612    iterator = iterator ? iterator.bind(context) : Prototype.K; 
    308613    var result = true; 
    309614    this.each(function(value, index) { 
    310       result = result && !!(iterator || Prototype.K)(value, index); 
     615      result = result && !!iterator(value, index); 
    311616      if (!result) throw $break; 
    312617    }); 
     
    314619  }, 
    315620 
    316   any: function(iterator) { 
     621  any: function(iterator, context) { 
     622    iterator = iterator ? iterator.bind(context) : Prototype.K; 
    317623    var result = false; 
    318624    this.each(function(value, index) { 
    319       if (result = !!(iterator || Prototype.K)(value, index)) 
     625      if (result = !!iterator(value, index)) 
    320626        throw $break; 
    321627    }); 
     
    323629  }, 
    324630 
    325   collect: function(iterator) { 
     631  collect: function(iterator, context) { 
     632    iterator = iterator ? iterator.bind(context) : Prototype.K; 
    326633    var results = []; 
    327634    this.each(function(value, index) { 
     
    331638  }, 
    332639 
    333   detect: function (iterator) { 
     640  detect: function(iterator, context) { 
     641    iterator = iterator.bind(context); 
    334642    var result; 
    335643    this.each(function(value, index) { 
     
    342650  }, 
    343651 
    344   findAll: function(iterator) { 
     652  findAll: function(iterator, context) { 
     653    iterator = iterator.bind(context); 
    345654    var results = []; 
    346655    this.each(function(value, index) { 
     
    351660  }, 
    352661 
    353   grep: function(pattern, iterator) { 
     662  grep: function(filter, iterator, context) { 
     663    iterator = iterator ? iterator.bind(context) : Prototype.K; 
    354664    var results = []; 
     665 
     666    if (Object.isString(filter)) 
     667      filter = new RegExp(filter); 
     668 
    355669    this.each(function(value, index) { 
    356       var stringValue = value.toString(); 
    357       if (stringValue.match(pattern)) 
    358         results.push((iterator || Prototype.K)(value, index)); 
    359     }) 
     670      if (filter.match(value)) 
     671        results.push(iterator(value, index)); 
     672    }); 
    360673    return results; 
    361674  }, 
    362675 
    363676  include: function(object) { 
     677    if (Object.isFunction(this.indexOf)) 
     678      if (this.indexOf(object) != -1) return true; 
     679 
    364680    var found = false; 
    365681    this.each(function(value) { 
     
    372688  }, 
    373689 
    374   inject: function(memo, iterator) { 
     690  inGroupsOf: function(number, fillWith) { 
     691    fillWith = Object.isUndefined(fillWith) ? null : fillWith; 
     692    return this.eachSlice(number, function(slice) { 
     693      while(slice.length < number) slice.push(fillWith); 
     694      return slice; 
     695    }); 
     696  }, 
     697 
     698  inject: function(memo, iterator, context) { 
     699    iterator = iterator.bind(context); 
    375700    this.each(function(value, index) { 
    376701      memo = iterator(memo, value, index); 
     
    381706  invoke: function(method) { 
    382707    var args = $A(arguments).slice(1); 
    383     return this.collect(function(value) { 
     708    return this.map(function(value) { 
    384709      return value[method].apply(value, args); 
    385710    }); 
    386711  }, 
    387712 
    388   max: function(iterator) { 
     713  max: function(iterator, context) { 
     714    iterator = iterator ? iterator.bind(context) : Prototype.K; 
    389715    var result; 
    390716    this.each(function(value, index) { 
    391       value = (iterator || Prototype.K)(value, index); 
    392       if (result == undefined || value >= result) 
     717      value = iterator(value, index); 
     718      if (result == null || value >= result) 
    393719        result = value; 
    394720    }); 
     
    396722  }, 
    397723 
    398   min: function(iterator) { 
     724  min: function(iterator, context) { 
     725    iterator = iterator ? iterator.bind(context) : Prototype.K; 
    399726    var result; 
    400727    this.each(function(value, index) { 
    401       value = (iterator || Prototype.K)(value, index); 
    402       if (result == undefined || value < result) 
     728      value = iterator(value, index); 
     729      if (result == null || value < result) 
    403730        result = value; 
    404731    }); 
     
    406733  }, 
    407734 
    408   partition: function(iterator) { 
     735  partition: function(iterator, context) { 
     736    iterator = iterator ? iterator.bind(context) : Prototype.K; 
    409737    var trues = [], falses = []; 
    410738    this.each(function(value, index) { 
    411       ((iterator || Prototype.K)(value, index) ? 
     739      (iterator(value, index) ? 
    412740        trues : falses).push(value); 
    413741    }); 
     
    417745  pluck: function(property) { 
    418746    var results = []; 
    419     this.each(function(value, index) { 
     747    this.each(function(value) { 
    420748      results.push(value[property]); 
    421749    }); 
     
    423751  }, 
    424752 
    425   reject: function(iterator) { 
     753  reject: function(iterator, context) { 
     754    iterator = iterator.bind(context); 
    426755    var results = []; 
    427756    this.each(function(value, index) { 
     
    432761  }, 
    433762 
    434   sortBy: function(iterator) { 
    435     return this.collect(function(value, index) { 
     763  sortBy: function(iterator, context) { 
     764    iterator = iterator.bind(context); 
     765    return this.map(function(value, index) { 
    436766      return {value: value, criteria: iterator(value, index)}; 
    437767    }).sort(function(left, right) { 
     
    442772 
    443773  toArray: function() { 
    444     return this.collect(Prototype.K); 
     774    return this.map(); 
    445775  }, 
    446776 
    447777  zip: function() { 
    448778    var iterator = Prototype.K, args = $A(arguments); 
    449     if (typeof args.last() == 'function'
     779    if (Object.isFunction(args.last())
    450780      iterator = args.pop(); 
    451781 
     
    456786  }, 
    457787 
     788  size: function() { 
     789    return this.toArray().length; 
     790  }, 
     791 
    458792  inspect: function() { 
    459793    return '#<Enumerable:' + this.toArray().inspect() + '>'; 
    460794  } 
    461 } 
     795}; 
    462796 
    463797Object.extend(Enumerable, { 
     
    465799  find:    Enumerable.detect, 
    466800  select:  Enumerable.findAll, 
     801  filter:  Enumerable.findAll, 
    467802  member:  Enumerable.include, 
    468   entries: Enumerable.toArray 
     803  entries: Enumerable.toArray, 
     804  every:   Enumerable.all, 
     805  some:    Enumerable.any 
    469806}); 
    470 var $A = Array.from = function(iterable) { 
     807function $A(iterable) { 
    471808  if (!iterable) return []; 
    472   if (iterable.toArray) { 
    473     return iterable.toArray(); 
    474   } else { 
    475     var results = []; 
    476     for (var i = 0; i < iterable.length; i++) 
    477       results.push(iterable[i]); 
     809  if (iterable.toArray) return iterable.toArray(); 
     810  var length = iterable.length || 0, results = new Array(length); 
     811  while (length--) results[length] = iterable[length]; 
     812  return results; 
     813
     814 
     815if (Prototype.Browser.WebKit) { 
     816  $A = function(iterable) { 
     817    if (!iterable) return []; 
     818    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && 
     819        iterable.toArray) return iterable.toArray(); 
     820    var length = iterable.length || 0, results = new Array(length); 
     821    while (length--) results[length] = iterable[length]; 
    478822    return results; 
    479   } 
     823  }; 
    480824} 
    481825 
     826Array.from = $A; 
     827 
    482828Object.extend(Array.prototype, Enumerable); 
    483829 
    484 if (!Array.prototype._reverse) 
    485   Array.prototype._reverse = Array.prototype.reverse; 
     830if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; 
    486831 
    487832Object.extend(Array.prototype, { 
    488833  _each: function(iterator) { 
    489     for (var i = 0; i < this.length; i++) 
     834    for (var i = 0, length = this.length; i < length; i++) 
    490835      iterator(this[i]); 
    491836  }, 
     
    506851  compact: function() { 
    507852    return this.select(function(value) { 
    508       return value != undefined || value != null; 
     853      return value != null; 
    509854    }); 
    510855  }, 
     
    512857  flatten: function() { 
    513858    return this.inject([], function(array, value) { 
    514       return array.concat(value && value.constructor == Array
     859      return array.concat(Object.isArray(value)