Changeset 1124

Show
Ignore:
Timestamp:
2008-07-25 12:49:59 (6 months ago)
Author:
gaspard
Message:

Added 'drag_handle' parameter to [draggable] and [each].
Updated js to prototype 1.6.
Fixed a bug in [drop] with multiple each (id was not set using '@node' in saved template).

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/app/helpers/application_helper.rb

    r1119 r1124  
    8686        page.visual_effect :highlight, params[:dom_id], :duration => 0.3 
    8787        page.visual_effect :fade, params[:dom_id], :duration => 0.3 
     88      when 'drop' 
     89        case params[:done] 
     90        when 'remove' 
     91          page.visual_effect :highlight, params[:drop], :duration => 0.3 
     92          page.visual_effect :fade, params[:drop], :duration => 0.3 
     93        end 
     94        page.replace params[:dom_id], :file => fullpath_from_template_url + ".erb" 
    8895      else 
    8996        page.replace params[:dom_id], :file => fullpath_from_template_url + ".erb" 
     
    11531160      attributes += options[:class] ? " class='#{options.delete(:class)}'" : '' 
    11541161      attributes += options[:id] ? " id='#{options.delete(:id)}'" : '' 
     1162      attributes += options[:name] ? " name='#{options.delete(:name)}'" : '' 
    11551163    end 
    11561164    url_only ? zen_path(node, options) :  "<a#{attributes} href='#{zen_path(node, options)}'>#{text}</a>" 
  • trunk/lib/parser/lib/rules/zafu.rb

    r1077 r1124  
    1212        html_tag_params[sym] = new_obj.params[sym] if new_obj.params.include?(sym) 
    1313      end 
    14       @html_tag          = new_obj.html_tag || @html_tag 
     14      @html_tag = new_obj.html_tag || @html_tag 
    1515      @html_tag_params.merge!(html_tag_params) 
     16      @method   = new_obj.params[:method] if new_obj.params[:method] 
    1617    end 
    1718     
  • trunk/lib/parser/lib/rules/zena.rb

    r1123 r1124  
    445445        @html_tag_done = false 
    446446        @html_tag_params.merge!(:id=>erb_dom_id) 
    447         out expand_with 
     447        @context[:scope_node] = node if @context[:scope_node] 
     448        out expand_with(:node => node) 
    448449        if @method == 'drop' && !@context[:make_form] 
    449450          out drop_javascript 
     
    457458          @html_tag ||= 'div' 
    458459          new_dom_scope 
     460           
    459461          unless @context[:make_form] 
    460462            # STORE TEMPLATE ======== 
     
    486488          @html_tag_params.merge!(:id=>erb_dom_id) 
    487489        end 
    488        
     490         
    489491        out expand_with 
    490492        if @method == 'drop' && !@context[:make_form] 
     
    601603         
    602604    def r_anchor(obj=node) 
    603       if @anchor_param =~ /\[(.+)\]/ 
    604         anchor_value = "<%= #{node_attribute($1)} %>" 
    605       else 
    606         anchor_value = "#{base_class.to_s.underscore}#{erb_node_id(obj)}" 
    607       end 
    608       "<a name='#{anchor_value}'></a>" 
     605      "<a name='#{anchor_name(@anchor_param, obj)}'></a>" 
     606    end 
     607     
     608    def anchor_name(p, obj=node) 
     609      if p =~ /\[(.+)\]/ 
     610        "<%= #{node_attribute($1)} %>" 
     611      else 
     612        "#{base_class.to_s.underscore}#{erb_node_id(obj)}" 
     613      end 
    609614    end 
    610615     
     
    10141019        if !descendant('form_tag') 
    10151020          # add a descendant before blocks. 
    1016           blocks_bak = @blocks.dup # I do not understand why we need 'dup' (but we sure do...) 
     1021          blocks_bak = @blocks 
     1022          @blocks = @blocks.dup 
    10171023          @blocks = [make(:void, :method=>'void', :text=>cancel)] + blocks_bak 
    10181024        else 
    10191025          form   = cancel + form 
    10201026          cancel = '' 
    1021           blocks_bak = @blocks 
    1022         end 
    1023       else 
    1024         blocks_bak = @blocks 
     1027        end 
    10251028      end 
    10261029       
    10271030      if append_submit 
    10281031        # add a descendant after blocks. 
     1032        unless blocks_bak 
     1033          blocks_bak = @blocks 
     1034          @blocks = @blocks.dup 
     1035        end 
    10291036        make(:void, :method=>'void', :text=>append_submit) 
    10301037      end 
     
    10351042        res = form + expand_with(:in_form => true, :form_cancel => cancel, :erb_dom_id => erb_dom_id, :dom_id => dom_id) + '</form>' 
    10361043      end 
    1037       @blocks = blocks_bak 
     1044       
     1045      @blocks = blocks_bak if blocks_bak 
     1046       
    10381047      @html_tag_done = false 
    10391048      @html_tag_params.merge!(id_hash) 
    10401049      out render_html_tag(res) 
    10411050    end 
    1042      
    1043      
    10441051     
    10451052    # <r:checkbox role='collaborator_for' values='projects' in='site'/>" 
     
    11921199        # set='icon_for=[id], v_status='50', v_title='[v_title]' 
    11931200        @params.each do |k, v| 
    1194           next if [:hover, :change].include?(k) 
     1201          next if [:hover, :change, :done].include?(k) 
    11951202          value, static = parse_attributes_in_value(v, :erb => false, :skip_node_attributes => true) 
    11961203          url_params << "node[#{k}]=#{CGI.escape(value)}" 
     
    11991206      end 
    12001207     
    1201       url_params << "&change=receiver" if change == 'receiver' 
    1202       url_params << "&t_url=#{CGI.escape(template_url)}" 
    1203       url_params << "&dom_id=#{erb_dom_id}" 
     1208      url_params << "change=receiver" if change == 'receiver' 
     1209      url_params << "t_url=#{CGI.escape(template_url)}" 
     1210      url_params << "dom_id=#{erb_dom_id}" 
     1211      url_params << "done=#{CGI.escape(@params[:done])}" if @params[:done] 
    12041212     
    12051213      "<script type='text/javascript'> 
     
    12241232        revert_effect = 'Element.move' 
    12251233      end 
    1226       out render_html_tag(expand_with) 
     1234      res = expand_with 
     1235      if @params[:drag_handle] 
     1236        drag_handle = @params[:drag_handle] == 'true' ? 'drag_hand' : @params[:drag_handle] 
     1237        if res =~ /class\s*=\s*['"]#{drag_handle}/ 
     1238          # nothing to do 
     1239          insert = '' 
     1240        else 
     1241          insert = "<span class='#{drag_handle}'>&nbsp;</span>" 
     1242        end 
     1243      else 
     1244        insert = '' 
     1245      end 
     1246      out render_html_tag(insert + res) 
     1247       
    12271248      out "<script type='text/javascript'> 
    1228       //<![CDATA[ 
    1229       Zena.draggable('#{@html_tag_params[:id]}',true,true,#{revert_effect}) 
    1230       //]]> 
    1231       </script>" 
     1249      //<![CDATA[" 
     1250      if drag_handle 
     1251        out "<script type='text/javascript'>\n//<![CDATA[\n 
     1252          new Draggable('#{@html_tag_params[:id]}', {ghosting:true, revert:true, revertEffect:#{revertEffect}, handle:$(dom_id).select('.#{drag_handle}')[0]}); 
     1253        });\n//]]>\n</script>" 
     1254      else 
     1255        out "<script type='text/javascript'>\n//<![CDATA[\nZena.draggable('#{@html_tag_params[:id]}',0,true,true,#{revert_effect})\n//]]>\n</script>" 
     1256      end 
    12321257    end 
    12331258  
     
    13731398        r_form 
    13741399      elsif @context[:list] 
    1375         # normal rendering: inserted into the layout 
     1400        # normal rendering: not the start of a saved template 
    13761401        if @params[:draggable] == 'true' || descendant('unlink') 
    13771402          @html_tag ||= 'div' 
     
    14111436        res = expand_with(:node => var, :scope_node => var) 
    14121437         
     1438        if @params[:drag_handle] && @params[:draggable] == 'true' 
     1439          drag_handle = @params[:drag_handle] == 'true' ? 'drag_handle' : @params[:drag_handle] 
     1440          if res =~ /class\s*=\s*['"]#{drag_handle}/ 
     1441            # nothing to do 
     1442            insert = '' 
     1443          else 
     1444            insert = "<span class='#{drag_handle}'>&nbsp;</span>" 
     1445          end 
     1446        else 
     1447          insert = '' 
     1448        end 
     1449         
    14131450        if id_hash 
    14141451          if @html_tag 
    14151452            @html_tag_params.merge!(id_hash) 
     1453            res = insert + res 
    14161454          else 
    1417             res = add_params(res, id_hash) 
    1418           end 
    1419         end 
     1455            res = add_params(res, id_hash, insert) 
     1456          end 
     1457        end 
     1458         
    14201459         
    14211460        out render_html_tag(res, html_append) 
     
    14241463         
    14251464        if @params[:draggable] == 'true' 
    1426           out "<script type='text/javascript'>\n//<![CDATA[\n<%= #{var}_dom_ids.inspect %>.each(Zena.draggable)\n//]]>\n</script>" 
     1465          if drag_handle 
     1466            out "<script type='text/javascript'>\n//<![CDATA[\n<%= #{var}_dom_ids.inspect %>.each(function(dom_id, index) { 
     1467                new Draggable(dom_id, {ghosting:true, revert:true, handle:$(dom_id).select('.#{drag_handle}')[0]}); 
     1468            });\n//]]>\n</script>" 
     1469          else 
     1470            out "<script type='text/javascript'>\n//<![CDATA[\n<%= #{var}_dom_ids.inspect %>.each(Zena.draggable)\n//]]>\n</script>" 
     1471          end 
    14271472        end 
    14281473         
    14291474      elsif @context[:saved_template] 
    1430         # render to produce a saved template 
     1475        # render to start a saved template 
    14311476        res = expand_with(:scope_node => node) 
     1477         
    14321478        if id_hash 
    14331479          if @html_tag 
     
    17331779        pre_space = @space_before || '' 
    17341780        @html_tag_done = true 
     1781      end 
     1782       
     1783      if @params[:anchor] 
     1784        @anchor_param = nil 
     1785        html_params[:name] = anchor_name(@params[:anchor], node) 
    17351786      end 
    17361787       
     
    22062257      res = initial_comma ? [""] : [] 
    22072258      params.each do |k,v| 
    2208         res << "#{k.inspect}=>#{v.inspect}" 
     2259        if v =~ /<%=/ && !(v =~ /"/) 
     2260          # replace by #{} 
     2261          val = v.gsub('#{', '# {').gsub(/<%=(.*?)%>/,'#{\1}') 
     2262          res << "#{k.inspect}=>\"#{val}\"" 
     2263        else 
     2264          res << "#{k.inspect}=>#{v.inspect}" 
     2265        end 
    22092266      end 
    22102267      res.join(', ') 
     
    24282485    end 
    24292486        
    2430     def add_params(text, opts={}
     2487    def add_params(text, opts={}, inner = ''
    24312488      text.sub(/\A([^<]*)<(\w+)(( .*?)[^%]|)>/) do 
    24322489        # we must set the first tag id 
     
    24382495          params[k] = v 
    24392496        end 
    2440         "#{before}<#{tag}#{params_to_html(params)}>
     2497        "#{before}<#{tag}#{params_to_html(params)}>#{inner}
    24412498      end 
    24422499    end 
  • trunk/lib/parser/test/parser/zafu.yml

    r1082 r1124  
    106106  res: "some [test {= :good=>'', :nice=>'work'}]things[/test] are fine" 
    107107 
    108  
    109108default_menu: 
    110109  src: "the <r:test>menu</r:test> is nice" 
     
    127126  res: "replace_named: named_with_name: <head>lalala</head>" 
    128127 
     128named_id: 
     129  src: "named_id: <p id='branding'>...</p>" 
     130  res: "named_id: <p id='branding'>...</p>" 
     131 
     132replace_with_do: 
     133  src: "replace_with_do: <r:include template='/named/id'><r:with part='branding' class='goal'>lalala</r:with></r:include>" 
     134  res: "replace_with_do: named_id: <p class='goal' id='branding'>lalala</p>" 
     135 
    129136named_with_do: 
    130   src: "named_with_do: <p id='branding'>...</p>" 
    131   res: "named_with_do: <p id='branding'>...</p>
    132  
    133 replace_with_do
    134   src: "replace_with_do: <r:include template='/named/with/do'><r:with part='branding' class='goal'>lalala</r:with>" 
    135   res: "replace_with_do: named_with_do: <p class='goal' id='branding'>lalala</p>" 
     137  src: "named_with_do: <p id='branding' do='test'>...</p>" 
     138  res: "named_with_do: [test {> :name=>'branding'}]<p id='branding'>...</p>[/test]
     139 
     140include_set_method
     141  src: "IKD: <r:include template='/named/with/do'><r:with part='branding' method='hello'>blah</r:with></r:include>" 
     142  res: "IKD: named_with_do: <p id='branding'>hello world!</p>" 
    136143 
    137144include_part: 
  • trunk/public/javascripts/controls.js

    r455 r1124  
    1 // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 
    2 //           (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan) 
    3 //           (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com) 
     1// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 
     2//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) 
     3//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) 
    44// Contributors: 
    55//  Richard Livsey 
     
    3838  throw("controls.js requires including script.aculo.us' effects.js library"); 
    3939 
    40 var Autocompleter = {} 
    41 Autocompleter.Base = function() {}; 
    42 Autocompleter.Base.prototype = { 
     40var Autocompleter = { } 
     41Autocompleter.Base = Class.create({ 
    4342  baseInitialize: function(element, update, options) { 
    44     this.element     = $(element);  
     43    element          = $(element) 
     44    this.element     = element;  
    4545    this.update      = $(update);   
    4646    this.hasFocus    = false;  
     
    4949    this.index       = 0;      
    5050    this.entryCount  = 0; 
     51    this.oldElementValue = this.element.value; 
    5152 
    5253    if(this.setOptions) 
    5354      this.setOptions(options); 
    5455    else 
    55       this.options = options || {}; 
     56      this.options = options || { }; 
    5657 
    5758    this.options.paramName    = this.options.paramName || this.element.name; 
     
    7576    if(typeof(this.options.tokens) == 'string')  
    7677      this.options.tokens = new Array(this.options.tokens); 
     78    // Force carriage returns as token delimiters anyway 
     79    if (!this.options.tokens.include('\n')) 
     80      this.options.tokens.push('\n'); 
    7781 
    7882    this.observer = null; 
     
    8286    Element.hide(this.update); 
    8387 
    84     Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); 
    85     Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); 
     88    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); 
     89    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); 
    8690  }, 
    8791 
     
    8993    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); 
    9094    if(!this.iefix &&  
    91       (navigator.appVersion.indexOf('MSIE')>0) && 
    92       (navigator.userAgent.indexOf('Opera')<0) && 
     95      (Prototype.Browser.IE) && 
    9396      (Element.getStyle(this.update, 'position')=='absolute')) { 
    9497      new Insertion.After(this.update,  
     
    140143         this.markPrevious(); 
    141144         this.render(); 
    142          if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); 
     145         Event.stop(event); 
    143146         return; 
    144147       case Event.KEY_DOWN: 
    145148         this.markNext(); 
    146149         this.render(); 
    147          if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); 
     150         Event.stop(event); 
    148151         return; 
    149152      } 
    150153     else  
    151154       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||  
    152          (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; 
     155         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; 
    153156 
    154157    this.changed = true; 
     
    196199          Element.addClassName(this.getEntry(i),"selected") :  
    197200          Element.removeClassName(this.getEntry(i),"selected"); 
    198          
    199201      if(this.hasFocus) {  
    200202        this.show(); 
     
    239241    var value = ''; 
    240242    if (this.options.select) { 
    241       var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; 
     243      var nodes = $(selectedElement).select('.' + this.options.select) || []; 
    242244      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); 
    243245    } else 
    244246      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); 
    245247     
    246     var lastTokenPos = this.findLastToken(); 
    247     if (lastTokenPos != -1) { 
    248       var newValue = this.element.value.substr(0, lastTokenPos + 1); 
    249       var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); 
     248    var bounds = this.getTokenBounds(); 
     249    if (bounds[0] != -1) { 
     250      var newValue = this.element.value.substr(0, bounds[0]); 
     251      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); 
    250252      if (whitespace) 
    251253        newValue += whitespace[0]; 
    252       this.element.value = newValue + value
     254      this.element.value = newValue + value + this.element.value.substr(bounds[1])
    253255    } else { 
    254256      this.element.value = value; 
    255257    } 
     258    this.oldElementValue = this.element.value; 
    256259    this.element.focus(); 
    257260     
     
    297300  onObserverEvent: function() { 
    298301    this.changed = false;    
     302    this.tokenBounds = null; 
    299303    if(this.getToken().length>=this.options.minChars) { 
    300       this.startIndicator(); 
    301304      this.getUpdatedChoices(); 
    302305    } else { 
     
    304307      this.hide(); 
    305308    } 
     309    this.oldElementValue = this.element.value; 
    306310  }, 
    307311 
    308312  getToken: function() { 
    309     var tokenPos = this.findLastToken(); 
    310     if (tokenPos != -1) 
    311       var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); 
    312     else 
    313       var ret = this.element.value; 
    314  
    315     return /\n/.test(ret) ? '' : ret; 
    316   }, 
    317  
    318   findLastToken: function() { 
    319     var lastTokenPos = -1; 
    320  
    321     for (var i=0; i<this.options.tokens.length; i++) { 
    322       var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]); 
    323       if (thisTokenPos > lastTokenPos) 
    324         lastTokenPos = thisTokenPos; 
    325     } 
    326     return lastTokenPos; 
     313    var bounds = this.getTokenBounds(); 
     314    return this.element.value.substring(bounds[0], bounds[1]).strip(); 
     315  }, 
     316 
     317  getTokenBounds: function() { 
     318    if (null != this.tokenBounds) return this.tokenBounds; 
     319    var value = this.element.value; 
     320    if (value.strip().empty()) return [-1, 0]; 
     321    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); 
     322    var offset = (diff == this.oldElementValue.length ? 1 : 0); 
     323    var prevTokenPos = -1, nextTokenPos = value.length; 
     324    var tp; 
     325    for (var index = 0, l = this.options.tokens.length; index < l; ++index) { 
     326      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); 
     327      if (tp > prevTokenPos) prevTokenPos = tp; 
     328      tp = value.indexOf(this.options.tokens[index], diff + offset); 
     329      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; 
     330    } 
     331    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); 
    327332  } 
    328 
    329  
    330 Ajax.Autocompleter = Class.create(); 
    331 Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { 
     333}); 
     334 
     335Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { 
     336  var boundary = Math.min(newS.length, oldS.length); 
     337  for (var index = 0; index < boundary; ++index) 
     338    if (newS[index] != oldS[index]) 
     339      return index; 
     340  return boundary; 
     341}; 
     342 
     343Ajax.Autocompleter = Class.create(Autocompleter.Base, { 
    332344  initialize: function(element, update, url, options) { 
    333345    this.baseInitialize(element, update, options); 
     
    339351 
    340352  getUpdatedChoices: function() { 
    341     entry = encodeURIComponent(this.options.paramName) + '=' +  
     353    this.startIndicator(); 
     354     
     355    var entry = encodeURIComponent(this.options.paramName) + '=' +  
    342356      encodeURIComponent(this.getToken()); 
    343357 
     
    347361    if(this.options.defaultParams)  
    348362      this.options.parameters += '&' + this.options.defaultParams; 
    349  
     363     
    350364    new Ajax.Request(this.url, this.options); 
    351365  }, 
     
    354368    this.updateChoices(request.responseText); 
    355369  } 
    356  
    357370}); 
    358371 
     
    392405// you support them. 
    393406 
    394 Autocompleter.Local = Class.create(); 
    395 Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { 
     407Autocompleter.Local = Class.create(Autocompleter.Base, { 
    396408  initialize: function(element, update, array, options) { 
    397409    this.baseInitialize(element, update, options); 
     
    449461        return "<ul>" + ret.join('') + "</ul>"; 
    450462      } 
    451     }, options || {}); 
     463    }, options || { }); 
    452464  } 
    453465}); 
    454466 
    455 // AJAX in-place editor 
    456 // 
    457 // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor 
     467// AJAX in-place editor and collection editor 
     468// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007). 
    458469 
    459470// Use this if you notice weird scrolling problems on some browsers, 
     
    466477} 
    467478 
    468 Ajax.InPlaceEditor = Class.create(); 
    469 Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; 
    470 Ajax.InPlaceEditor.prototype = { 
     479Ajax.InPlaceEditor = Class.create({ 
    471480  initialize: function(element, url, options) { 
    472481    this.url = url; 
    473     this.element = $(element); 
    474  
    475     this.options = Object.extend({ 
    476       paramName: "value", 
    477       okButton: true, 
    478       okText: "ok", 
    479       cancelLink: true, 
    480       cancelText: "cancel", 
    481       savingText: "Saving...", 
    482       clickToEditText: "Click to edit", 
    483       okText: "ok", 
    484       rows: 1, 
    485       onComplete: function(transport, element) { 
    486         new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); 
    487       }, 
    488       onFailure: function(transport) { 
    489         alert("Error communicating with the server: " + transport.responseText.stripTags()); 
    490       }, 
    491       callback: function(form) { 
    492         return Form.serialize(form); 
    493       }, 
    494       handleLineBreaks: true, 
    495       loadingText: 'Loading...', 
    496       savingClassName: 'inplaceeditor-saving', 
    497       loadingClassName: 'inplaceeditor-loading', 
    498       formClassName: 'inplaceeditor-form', 
    499       highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, 
    500       highlightendcolor: "#FFFFFF", 
    501       externalControl: null, 
    502       submitOnBlur: false, 
    503       ajaxOptions: {}, 
    504       evalScripts: false 
    505     }, options || {}); 
    506  
    507     if(!this.options.formId && this.element.id) { 
    508       this.options.formId = this.element.id + "-inplaceeditor"; 
    509       if ($(this.options.formId)) { 
    510         // there's already a form with that name, don't specify an id 
    511         this.options.formId = null; 
    512       } 
    513     } 
    514      
    515     if (this.options.externalControl) { 
     482    this.element = element = $(element); 
     483    this.prepareOptions(); 
     484    this._controls = { }; 
     485    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! 
     486    Object.extend(this.options, options || { }); 
     487    if (!this.options.formId && this.element.id) { 
     488      this.options.formId = this.element.id + '-inplaceeditor'; 
     489      if ($(this.options.formId)) 
     490        this.options.formId = ''; 
     491    } 
     492    if (this.options.externalControl) 
    516493      this.options.externalControl = $(this.options.externalControl); 
    517     } 
    518      
    519     this.originalBackground = Element.getStyle(this.element, 'background-color'); 
    520     if (!this.originalBackground) { 
    521       this.originalBackground = "transparent"; 
    522     } 
    523      
     494    if (!this.options.externalControl) 
     495      this.options.externalControlOnly = false; 
     496    this._originalBackground = this.element.getStyle('background-color') || 'transparent'; 
    524497    this.element.title = this.options.clickToEditText; 
    525      
    526     this.onclickListener = this.enterEditMode.bindAsEventListener(this); 
    527     this.mouseoverListener = this.enterHover.bindAsEventListener(this); 
    528     this.mouseoutListener = this.leaveHover.bindAsEventListener(this); 
    529     Event.observe(this.element, 'click', this.onclickListener); 
    530     Event.observe(this.element, 'mouseover', this.mouseoverListener); 
    531     Event.observe(this.element, 'mouseout', this.mouseoutListener); 
    532     if (this.options.externalControl) { 
    533       Event.observe(this.options.externalControl, 'click', this.onclickListener); 
    534       Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); 
    535       Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); 
    536     } 
    537   }, 
    538   enterEditMode: function(evt) { 
    539     if (this.saving) return; 
    540     if (this.editing) return; 
    541     this.editing = true; 
    542     this.onEnterEditMode(); 
    543     if (this.options.externalControl) { 
    544       Element.hide(this.options.externalControl); 
    545     } 
    546     Element.hide(this.element); 
     498    this._boundCancelHandler = this.handleFormCancellation.bind(this); 
     499    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); 
     500    this._boundFailureHandler = this.handleAJAXFailure.bind(this); 
     501    this._boundSubmitHandler = this.handleFormSubmission.bind(this); 
     502    this._boundWrapperHandler = this.wrapUp.bind(this); 
     503    this.registerListeners(); 
     504  }, 
     505  checkForEscapeOrReturn: function(e) { 
     506    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; 
     507    if (Event.KEY_ESC == e.keyCode) 
     508      this.handleFormCancellation(e); 
     509    else if (Event.KEY_RETURN == e.keyCode) 
     510      this.handleFormSubmission(e); 
     511  }, 
     512  createControl: function(mode, handler, extraClasses) { 
     513    var control = this.options[mode + 'Control']; 
     514    var text = this.options[mode + 'Text']; 
     515    if ('button' == control) { 
     516      var btn = document.createElement('input'); 
     517      btn.type = 'submit'; 
     518      btn.value = text; 
     519      btn.className = 'editor_' + mode + '_button'; 
     520      if ('cancel' == mode) 
     521        btn.onclick = this._boundCancelHandler; 
     522      this._form.appendChild(btn); 
     523      this._controls[mode] = btn; 
     524    } else if ('link' == control) { 
     525      var link = document.createElement('a'); 
     526      link.href = '#'; 
     527      link.appendChild(document.createTextNode(text)); 
     528      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; 
     529      link.className = 'editor_' + mode + '_link'; 
     530      if (extraClasses) 
     531        link.className += ' ' + extraClasses; 
     532      this._form.appendChild(link); 
     533      this._controls[mode] = link; 
     534    } 
     535  }, 
     536  createEditField: function() { 
     537    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); 
     538    var fld; 
     539    if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { 
     540      fld = document.createElement('input'); 
     541      fld.type = 'text'; 
     542      var size = this.options.size || this.options.cols || 0; 
     543      if (0 < size) fld.size = size; 
     544    } else { 
     545      fld = document.createElement('textarea'); 
     546      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); 
     547      fld.cols = this.options.cols || 40; 
     548    } 
     549    fld.name = this.options.paramName; 
     550    fld.value = text; // No HTML breaks conversion anymore 
     551    fld.className = 'editor_field'; 
     552    if (this.options.submitOnBlur) 
     553      fld.onblur = this._boundSubmitHandler; 
     554    this._controls.editor = fld; 
     555    if (this.options.loadTextURL) 
     556      this.loadExternalText(); 
     557    this._form.appendChild(this._controls.editor); 
     558  }, 
     559  createForm: function() { 
     560    var ipe = this; 
     561    function addText(mode, condition) { 
     562      var text = ipe.options['text' + mode + 'Controls']; 
     563      if (!text || condition === false) return; 
     564      ipe._form.appendChild(document.createTextNode(text)); 
     565    }; 
     566    this._form = $(document.createElement('form')); 
     567    this._form.id = this.options.formId; 
     568    this._form.addClassName(this.options.formClassName); 
     569    this._form.onsubmit = this._boundSubmitHandler; 
     570    this.createEditField(); 
     571    if ('textarea' == this._controls.editor.tagName.toLowerCase()) 
     572      this._form.appendChild(document.createElement('br')); 
     573    if (this.options.onFormCustomization) 
     574      this.options.onFormCustomization(this, this._form); 
     575    addText('Before', this.options.okControl || this.options.cancelControl); 
     576    this.createControl('ok', this._boundSubmitHandler); 
     577    addText('Between', this.options.okControl && this.options.cancelControl); 
     578    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); 
     579    addText('After', this.options.okControl || this.options.cancelControl); 
     580  }, 
     581  destroy: function() { 
     582    if (this._oldInnerHTML) 
     583      this.element.innerHTML = this._oldInnerHTML; 
     584    this.leaveEditMode(); 
     585    this.unregisterListeners(); 
     586  }, 
     587  enterEditMode: function(e) { 
     588    if (this._saving || this._editing) return; 
     589    this._editing = true; 
     590    this.triggerCallback('onEnterEditMode'); 
     591    if (this.options.externalControl) 
     592      this.options.externalControl.hide(); 
     593    this.element.hide(); 
    547594    this.createForm(); 
    548     this.element.parentNode.insertBefore(this.form, this.element); 
    549     if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField); 
    550     // stop the event to avoid a page refresh in Safari 
    551     if (evt) { 
    552       Event.stop(evt); 
    553     } 
    554     return false; 
    555   }, 
    556   createForm: function() { 
    557     this.form = document.createElement("form"); 
    558     this.form.id = this.options.formId; 
    559     Element.addClassName(this.form, this.options.formClassName) 
    560     this.form.onsubmit = this.onSubmit.bind(this); 
    561  
    562     this.createEditField(); 
    563  
    564     if (this.options.textarea) { 
    565       var br = document.createElement("br"); 
    566       this.form.appendChild(br); 
    567     } 
    568  
    569     if (this.options.okButton) { 
    570       okButton = document.createElement("input"); 
    571       okButton.type = "submit"; 
    572       okButton.value = this.options.okText; 
    573       okButton.className = 'editor_ok_button'; 
    574       this.form.appendChild(okButton); 
    575     } 
    576  
    577     if (this.options.cancelLink) { 
    578       cancelLink = document.createElement("a"); 
    579       cancelLink.href = "#"; 
    580       cancelLink.appendChild(document.createTextNode(this.options.cancelText)); 
    581       cancelLink.onclick = this.onclickCancel.bind(this); 
    582       cancelLink.className = 'editor_cancel';       
    583       this.form.appendChild(cancelLink); 
    584     } 
    585   }, 
    586   hasHTMLLineBreaks: function(string) { 
    587     if (!this.options.handleLineBreaks) return false; 
    588     return string.match(/<br/i) || string.match(/<p>/i); 
    589   }, 
    590   convertHTMLLineBreaks: function(string) { 
    591     return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, ""); 
    592   }, 
    593   createEditField: function() { 
    594     var text; 
    595     if(this.options.loadTextURL) { 
    596       text = this.options.loadingText; 
    597     } else { 
    598       text = this.getText(); 
    599     } 
    600  
    601     var obj = this; 
    602      
    603     if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { 
    604       this.options.textarea = false; 
    605       var textField = document.createElement("input"); 
    606       textField.obj = this; 
    607       textField.type = "text"; 
    608       textField.name = this.options.paramName; 
    609       textField.value = text; 
    610       textField.style.backgroundColor = this.options.highlightcolor; 
    611       textField.className = 'editor_field'; 
    612       var size = this.options.size || this.options.cols || 0; 
    613       if (size != 0) textField.size = size; 
    614       if (this.options.submitOnBlur) 
    615         textField.onblur = this.onSubmit.bind(this); 
    616       this.editField = textField; 
    617     } else { 
    618       this.options.textarea = true; 
    619       var textArea = document.createElement("textarea"); 
    620       textArea.obj = this; 
    621       textArea.name = this.options.paramName; 
    622       textArea.value = this.convertHTMLLineBreaks(text); 
    623       textArea.rows = this.options.rows; 
    624       textArea.cols = this.options.cols || 40; 
    625       textArea.className = 'editor_field';       
    626       if (this.options.submitOnBlur) 
    627         textArea.onblur = this.onSubmit.bind(this); 
    628       this.editField = textArea; 
    629     } 
    630      
    631     if(this.options.loadTextURL) { 
    632       this.loadExternalText(); 
    633     } 
    634     this.form.appendChild(this.editField); 
     595    this.element.parentNode.insertBefore(this._form, this.element); 
     596    if (!this.options.loadTextURL) 
     597      this.postProcessEditField(); 
     598    if (e) Event.stop(e); 
     599  }, 
     600  enterHover: function(e) { 
     601    if (this.options.hoverClassName) 
     602      this.element.addClassName(this.options.hoverClassName); 
     603    if (this._saving) return; 
     604    this.triggerCallback('onEnterHover'); 
    635605  }, 
    636606  getText: function() { 
    637607    return this.element.innerHTML; 
    638608  }, 
     609  handleAJAXFailure: function(transport) { 
     610    this.triggerCallback('onFailure', transport); 
     611    if (this._oldInnerHTML) { 
     612      this.element.innerHTML = this._oldInnerHTML; 
     613      this._oldInnerHTML = null; 
     614    } 
     615  }, 
     616  handleFormCancellation: function(e) { 
     617    this.wrapUp(); 
     618    if (e) Event.stop(e); 
     619  }, 
     620  handleFormSubmission: function(e) { 
     621    var form = this._form; 
     622    var value = $F(this._controls.editor); 
     623    this.prepareSubmission(); 
     624    var params = this.options.callback(form, value) || ''; 
     625    if (Object.isString(params)) 
     626      params = params.toQueryParams(); 
     627    params.editorId = this.element.id; 
     628    if (this.options.htmlResponse) { 
     629      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); 
     630      Object.extend(options, { 
     631        parameters: params, 
     632        onComplete: this._boundWrapperHandler, 
     633        onFailure: this._boundFailureHandler 
     634      }); 
     635      new Ajax.Updater({ success: this.element }, this.url, options); 
     636    } else { 
     637      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); 
     638      Object.extend(options, { 
     639        parameters: params, 
     640        onComplete: this._boundWrapperHandler, 
     641        onFailure: this._boundFailureHandler 
     642      }); 
     643      new Ajax.Request(this.url, options); 
     644    } 
     645    if (e) Event.stop(e); 
     646  }, 
     647  leaveEditMode: function() { 
     648    this.element.removeClassName(this.options.savingClassName); 
     649    this.removeForm(); 
     650    this.leaveHover(); 
     651    this.element.style.backgroundColor = this._originalBackground; 
     652    this.element.show(); 
     653    if (this.options.externalControl) 
     654      this.options.externalControl.show(); 
     655    this._saving = false; 
     656    this._editing = false; 
     657    this._oldInnerHTML = null; 
     658    this.triggerCallback('onLeaveEditMode'); 
     659  }, 
     660  leaveHover: function(e) { 
     661    if (this.options.hoverClassName) 
     662      this.element.removeClassName(this.options.hoverClassName); 
     663    if (this._saving) return; 
     664    this.triggerCallback('onLeaveHover'); 
     665  }, 
    639666  loadExternalText: function() { 
    640     Element.addClassName(this.form, this.options.loadingClassName); 
    641     this.editField.disabled = true; 
    642     new Ajax.Request( 
    643       this.options.loadTextURL, 
    644       Object.extend({ 
    645         asynchronous: true, 
    646         onComplete: this.onLoadedExternalText.bind(this) 
    647       }, this.options.ajaxOptions) 
    648     ); 
    649   }, 
    650   onLoadedExternalText: function(transport) { 
    651     Element.removeClassName(this.form, this.options.loadingClassName); 
    652     this.editField.disabled = false; 
    653     this.editField.value = transport.responseText.stripTags(); 
    654     Field.scrollFreeActivate(this.editField); 
    655   }, 
    656   onclickCancel: function() { 
    657     this.onComplete(); 
    658     this.leaveEditMode(); 
    659     return false; 
    660   }, 
    661   onFailure: function(transport) { 
    662     this.options.onFailure(transport); 
    663     if (this.oldInnerHTML) { 
    664       this.element.innerHTML = this.oldInnerHTML; 
    665       this.oldInnerHTML = null; 
    666     } 
    667     return false; 
    668   }, 
    669   onSubmit: function() { 
    670     // onLoading resets these so we need to save them away for the Ajax call 
    671     var form = this.form; 
    672     var value = this.editField.value; 
    673      
    674     // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... 
    675     // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... 
    676     // to be displayed indefinitely 
    677     this.onLoading(); 
    678      
    679     if (this.options.evalScripts) { 
    680       new Ajax.Request( 
    681         this.url, Object.extend({ 
    682           parameters: this.options.callback(form, value), 
    683           onComplete: this.onComplete.bind(this), 
    684           onFailure: this.onFailure.bind(this), 
    685           asynchronous:true,  
    686           evalScripts:true 
    687         }, this.options.ajaxOptions)); 
    688     } else  { 
    689       new Ajax.Updater( 
    690         { success: this.element, 
    691           // don't update on failure (this could be an option) 
    692           failure: null },  
    693         this.url, Object.extend({ 
    694           parameters: this.options.callback(form, value), 
    695           onComplete: this.onComplete.bind(this), 
    696           onFailure: this.onFailure.bind(this) 
    697         }, this.options.ajaxOptions)); 
    698     } 
    699     // stop the event to avoid a page refresh in Safari 
    700     if (arguments.length > 1) { 
    701       Event.stop(arguments[0]); 
    702     } 
    703     return false; 
    704   }, 
    705   onLoading: function() { 
    706     this.saving = true; 
     667    this._form.addClassName(this.options.loadingClassName); 
     668    this._controls.editor.disabled = true; 
     669    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); 
     670    Object.extend(options, { 
     671      parameters: 'editorId=' + encodeURIComponent(this.element.id), 
     672      onComplete: Prototype.emptyFunction, 
     673      onSuccess: function(transport) { 
     674        this._form.removeClassName(this.options.loadingClassName); 
     675        var text = transport.responseText; 
     676        if (this.options.stripLoadedTextTags) 
     677          text = text.stripTags(); 
     678        this._controls.editor.value = text; 
     679        this._controls.editor.disabled = false; 
     680        this.postProcessEditField(); 
     681      }.bind(this), 
     682      onFailure: this._boundFailureHandler 
     683    }); 
     684    new Ajax.Request(this.options.loadTextURL, options); 
     685  }, 
     686  postProcessEditField: function() { 
     687    var fpc = this.options.fieldPostCreation; 
     688    if (fpc) 
     689      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); 
     690  }, 
     691  prepareOptions: function() { 
     692    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); 
     693    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); 
     694    [this._extraDefaultOptions].flatten().compact().each(function(defs) { 
     695      Object.extend(this.options, defs); 
     696    }.bind(this)); 
     697  }, 
     698  prepareSubmission: function() { 
     699    this._saving = true; 
    707700    this.removeForm(); 
    708701    this.leaveHover(); 
    709702    this.showSaving(); 
    710703  }, 
     704  registerListeners: function() { 
     705    this._listeners = { }; 
     706    var listener; 
     707    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { 
     708      listener = this[pair.value].bind(this); 
     709      this._listeners[pair.key] = listener; 
     710      if (!this.options.externalControlOnly) 
     711        this.element.observe(pair.key, listener); 
     712      if (this.options.externalControl) 
     713        this.options.externalControl.observe(pair.key, listener); 
     714    }.bind(this)); 
     715  }, 
     716  removeForm: function() { 
     717    if (!this._form) return; 
     718    this._form.remove(); 
     719    this._form = null; 
     720    this._controls = { }; 
     721  }, 
    711722  showSaving: function() { 
    712     this.oldInnerHTML = this.element.innerHTML; 
     723    this._oldInnerHTML = this.element.innerHTML; 
    713724    this.element.innerHTML = this.options.savingText; 
    714     Element.addClassName(this.element, this.options.s