Changeset 1124
- Timestamp:
- 2008-07-25 12:49:59 (6 months ago)
- Files:
-
- trunk/app/helpers/application_helper.rb (modified) (2 diffs)
- trunk/lib/parser/lib/rules/zafu.rb (modified) (1 diff)
- trunk/lib/parser/lib/rules/zena.rb (modified) (16 diffs)
- trunk/lib/parser/test/parser/zafu.yml (modified) (2 diffs)
- trunk/public/javascripts/controls.js (modified) (19 diffs)
- trunk/public/javascripts/dragdrop.js (modified) (29 diffs)
- trunk/public/javascripts/effects.js (modified) (50 diffs)
- trunk/public/javascripts/prototype.js (modified) (84 diffs)
- trunk/public/stylesheets/zena.css (modified) (1 diff)
- trunk/test/helpers/zena_parser/ajax.yml (modified) (1 diff)
- trunk/test/helpers/zena_parser/basic.yml (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/app/helpers/application_helper.rb
r1119 r1124 86 86 page.visual_effect :highlight, params[:dom_id], :duration => 0.3 87 87 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" 88 95 else 89 96 page.replace params[:dom_id], :file => fullpath_from_template_url + ".erb" … … 1153 1160 attributes += options[:class] ? " class='#{options.delete(:class)}'" : '' 1154 1161 attributes += options[:id] ? " id='#{options.delete(:id)}'" : '' 1162 attributes += options[:name] ? " name='#{options.delete(:name)}'" : '' 1155 1163 end 1156 1164 url_only ? zen_path(node, options) : "<a#{attributes} href='#{zen_path(node, options)}'>#{text}</a>" trunk/lib/parser/lib/rules/zafu.rb
r1077 r1124 12 12 html_tag_params[sym] = new_obj.params[sym] if new_obj.params.include?(sym) 13 13 end 14 @html_tag = new_obj.html_tag || @html_tag14 @html_tag = new_obj.html_tag || @html_tag 15 15 @html_tag_params.merge!(html_tag_params) 16 @method = new_obj.params[:method] if new_obj.params[:method] 16 17 end 17 18 trunk/lib/parser/lib/rules/zena.rb
r1123 r1124 445 445 @html_tag_done = false 446 446 @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) 448 449 if @method == 'drop' && !@context[:make_form] 449 450 out drop_javascript … … 457 458 @html_tag ||= 'div' 458 459 new_dom_scope 460 459 461 unless @context[:make_form] 460 462 # STORE TEMPLATE ======== … … 486 488 @html_tag_params.merge!(:id=>erb_dom_id) 487 489 end 488 490 489 491 out expand_with 490 492 if @method == 'drop' && !@context[:make_form] … … 601 603 602 604 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 609 614 end 610 615 … … 1014 1019 if !descendant('form_tag') 1015 1020 # 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 1017 1023 @blocks = [make(:void, :method=>'void', :text=>cancel)] + blocks_bak 1018 1024 else 1019 1025 form = cancel + form 1020 1026 cancel = '' 1021 blocks_bak = @blocks 1022 end 1023 else 1024 blocks_bak = @blocks 1027 end 1025 1028 end 1026 1029 1027 1030 if append_submit 1028 1031 # add a descendant after blocks. 1032 unless blocks_bak 1033 blocks_bak = @blocks 1034 @blocks = @blocks.dup 1035 end 1029 1036 make(:void, :method=>'void', :text=>append_submit) 1030 1037 end … … 1035 1042 res = form + expand_with(:in_form => true, :form_cancel => cancel, :erb_dom_id => erb_dom_id, :dom_id => dom_id) + '</form>' 1036 1043 end 1037 @blocks = blocks_bak 1044 1045 @blocks = blocks_bak if blocks_bak 1046 1038 1047 @html_tag_done = false 1039 1048 @html_tag_params.merge!(id_hash) 1040 1049 out render_html_tag(res) 1041 1050 end 1042 1043 1044 1051 1045 1052 # <r:checkbox role='collaborator_for' values='projects' in='site'/>" … … 1192 1199 # set='icon_for=[id], v_status='50', v_title='[v_title]' 1193 1200 @params.each do |k, v| 1194 next if [:hover, :change ].include?(k)1201 next if [:hover, :change, :done].include?(k) 1195 1202 value, static = parse_attributes_in_value(v, :erb => false, :skip_node_attributes => true) 1196 1203 url_params << "node[#{k}]=#{CGI.escape(value)}" … … 1199 1206 end 1200 1207 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] 1204 1212 1205 1213 "<script type='text/javascript'> … … 1224 1232 revert_effect = 'Element.move' 1225 1233 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}'> </span>" 1242 end 1243 else 1244 insert = '' 1245 end 1246 out render_html_tag(insert + res) 1247 1227 1248 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 1232 1257 end 1233 1258 … … 1373 1398 r_form 1374 1399 elsif @context[:list] 1375 # normal rendering: inserted into the layout1400 # normal rendering: not the start of a saved template 1376 1401 if @params[:draggable] == 'true' || descendant('unlink') 1377 1402 @html_tag ||= 'div' … … 1411 1436 res = expand_with(:node => var, :scope_node => var) 1412 1437 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}'> </span>" 1445 end 1446 else 1447 insert = '' 1448 end 1449 1413 1450 if id_hash 1414 1451 if @html_tag 1415 1452 @html_tag_params.merge!(id_hash) 1453 res = insert + res 1416 1454 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 1420 1459 1421 1460 out render_html_tag(res, html_append) … … 1424 1463 1425 1464 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 1427 1472 end 1428 1473 1429 1474 elsif @context[:saved_template] 1430 # render to producea saved template1475 # render to start a saved template 1431 1476 res = expand_with(:scope_node => node) 1477 1432 1478 if id_hash 1433 1479 if @html_tag … … 1733 1779 pre_space = @space_before || '' 1734 1780 @html_tag_done = true 1781 end 1782 1783 if @params[:anchor] 1784 @anchor_param = nil 1785 html_params[:name] = anchor_name(@params[:anchor], node) 1735 1786 end 1736 1787 … … 2206 2257 res = initial_comma ? [""] : [] 2207 2258 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 2209 2266 end 2210 2267 res.join(', ') … … 2428 2485 end 2429 2486 2430 def add_params(text, opts={} )2487 def add_params(text, opts={}, inner = '') 2431 2488 text.sub(/\A([^<]*)<(\w+)(( .*?)[^%]|)>/) do 2432 2489 # we must set the first tag id … … 2438 2495 params[k] = v 2439 2496 end 2440 "#{before}<#{tag}#{params_to_html(params)}> "2497 "#{before}<#{tag}#{params_to_html(params)}>#{inner}" 2441 2498 end 2442 2499 end trunk/lib/parser/test/parser/zafu.yml
r1082 r1124 106 106 res: "some [test {= :good=>'', :nice=>'work'}]things[/test] are fine" 107 107 108 109 108 default_menu: 110 109 src: "the <r:test>menu</r:test> is nice" … … 127 126 res: "replace_named: named_with_name: <head>lalala</head>" 128 127 128 named_id: 129 src: "named_id: <p id='branding'>...</p>" 130 res: "named_id: <p id='branding'>...</p>" 131 132 replace_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 129 136 named_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 140 include_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>" 136 143 137 144 include_part: trunk/public/javascripts/controls.js
r455 r1124 1 // Copyright (c) 2005 , 2006Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)2 // (c) 2005 , 2006Ivan Krstic (http://blogs.law.harvard.edu/ivan)3 // (c) 2005 , 2006Jon 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) 4 4 // Contributors: 5 5 // Richard Livsey … … 38 38 throw("controls.js requires including script.aculo.us' effects.js library"); 39 39 40 var Autocompleter = {} 41 Autocompleter.Base = function() {}; 42 Autocompleter.Base.prototype = { 40 var Autocompleter = { } 41 Autocompleter.Base = Class.create({ 43 42 baseInitialize: function(element, update, options) { 44 this.element = $(element); 43 element = $(element) 44 this.element = element; 45 45 this.update = $(update); 46 46 this.hasFocus = false; … … 49 49 this.index = 0; 50 50 this.entryCount = 0; 51 this.oldElementValue = this.element.value; 51 52 52 53 if(this.setOptions) 53 54 this.setOptions(options); 54 55 else 55 this.options = options || { };56 this.options = options || { }; 56 57 57 58 this.options.paramName = this.options.paramName || this.element.name; … … 75 76 if(typeof(this.options.tokens) == 'string') 76 77 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'); 77 81 78 82 this.observer = null; … … 82 86 Element.hide(this.update); 83 87 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)); 86 90 }, 87 91 … … 89 93 if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); 90 94 if(!this.iefix && 91 (navigator.appVersion.indexOf('MSIE')>0) && 92 (navigator.userAgent.indexOf('Opera')<0) && 95 (Prototype.Browser.IE) && 93 96 (Element.getStyle(this.update, 'position')=='absolute')) { 94 97 new Insertion.After(this.update, … … 140 143 this.markPrevious(); 141 144 this.render(); 142 if(navigator.appVersion.indexOf('AppleWebKit')>0)Event.stop(event);145 Event.stop(event); 143 146 return; 144 147 case Event.KEY_DOWN: 145 148 this.markNext(); 146 149 this.render(); 147 if(navigator.appVersion.indexOf('AppleWebKit')>0)Event.stop(event);150 Event.stop(event); 148 151 return; 149 152 } 150 153 else 151 154 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; 153 156 154 157 this.changed = true; … … 196 199 Element.addClassName(this.getEntry(i),"selected") : 197 200 Element.removeClassName(this.getEntry(i),"selected"); 198 199 201 if(this.hasFocus) { 200 202 this.show(); … … 239 241 var value = ''; 240 242 if (this.options.select) { 241 var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];243 var nodes = $(selectedElement).select('.' + this.options.select) || []; 242 244 if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); 243 245 } else 244 246 value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); 245 247 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+/); 250 252 if (whitespace) 251 253 newValue += whitespace[0]; 252 this.element.value = newValue + value ;254 this.element.value = newValue + value + this.element.value.substr(bounds[1]); 253 255 } else { 254 256 this.element.value = value; 255 257 } 258 this.oldElementValue = this.element.value; 256 259 this.element.focus(); 257 260 … … 297 300 onObserverEvent: function() { 298 301 this.changed = false; 302 this.tokenBounds = null; 299 303 if(this.getToken().length>=this.options.minChars) { 300 this.startIndicator();301 304 this.getUpdatedChoices(); 302 305 } else { … … 304 307 this.hide(); 305 308 } 309 this.oldElementValue = this.element.value; 306 310 }, 307 311 308 312 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]); 327 332 } 328 } 329 330 Ajax.Autocompleter = Class.create(); 331 Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { 333 }); 334 335 Autocompleter.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 343 Ajax.Autocompleter = Class.create(Autocompleter.Base, { 332 344 initialize: function(element, update, url, options) { 333 345 this.baseInitialize(element, update, options); … … 339 351 340 352 getUpdatedChoices: function() { 341 entry = encodeURIComponent(this.options.paramName) + '=' + 353 this.startIndicator(); 354 355 var entry = encodeURIComponent(this.options.paramName) + '=' + 342 356 encodeURIComponent(this.getToken()); 343 357 … … 347 361 if(this.options.defaultParams) 348 362 this.options.parameters += '&' + this.options.defaultParams; 349 363 350 364 new Ajax.Request(this.url, this.options); 351 365 }, … … 354 368 this.updateChoices(request.responseText); 355 369 } 356 357 370 }); 358 371 … … 392 405 // you support them. 393 406 394 Autocompleter.Local = Class.create(); 395 Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { 407 Autocompleter.Local = Class.create(Autocompleter.Base, { 396 408 initialize: function(element, update, array, options) { 397 409 this.baseInitialize(element, update, options); … … 449 461 return "<ul>" + ret.join('') + "</ul>"; 450 462 } 451 }, options || { });463 }, options || { }); 452 464 } 453 465 }); 454 466 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). 458 469 459 470 // Use this if you notice weird scrolling problems on some browsers, … … 466 477 } 467 478 468 Ajax.InPlaceEditor = Class.create(); 469 Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; 470 Ajax.InPlaceEditor.prototype = { 479 Ajax.InPlaceEditor = Class.create({ 471 480 initialize: function(element, url, options) { 472 481 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) 516 493 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'; 524 497 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(); 547 594 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'); 635 605 }, 636 606 getText: function() { 637 607 return this.element.innerHTML; 638 608 }, 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 }, 639 666 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; 707 700 this.removeForm(); 708 701 this.leaveHover(); 709 702 this.showSaving(); 710 703 }, 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 }, 711 722 showSaving: function() { 712 this. oldInnerHTML = this.element.innerHTML;723 this._oldInnerHTML = this.element.innerHTML; 713 724 this.element.innerHTML = this.options.savingText; 714 Element.addClassName(this.element, this.options.s
