backlog 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. data/History.txt +15 -0
  2. data/app/controllers/application.rb +17 -0
  3. data/app/controllers/customers_controller.rb +51 -0
  4. data/app/controllers/estimates_controller.rb +3 -4
  5. data/app/controllers/tasks_controller.rb +20 -32
  6. data/app/helpers/application_helper.rb +11 -2
  7. data/app/helpers/customers_helper.rb +2 -0
  8. data/app/helpers/tasks_helper.rb +69 -0
  9. data/app/views/customers/_form.rhtml +7 -0
  10. data/app/views/customers/edit.rhtml +9 -0
  11. data/app/views/customers/list.rhtml +27 -0
  12. data/app/views/customers/new.rhtml +8 -0
  13. data/app/views/customers/show.rhtml +8 -0
  14. data/app/views/periods/_show_active.rhtml +13 -10
  15. data/app/views/redirect.rjs +1 -0
  16. data/app/views/redirect.rjs~ +0 -0
  17. data/app/views/tasks/_fields_header.rhtml +1 -1
  18. data/app/views/tasks/_form.rhtml +6 -1
  19. data/app/views/tasks/_task.rhtml +3 -3
  20. data/app/views/tasks/finish.rjs +4 -0
  21. data/app/views/tasks/move_to_period.rjs +4 -0
  22. data/app/views/tasks/reopen.rjs +4 -24
  23. data/config/boot.rb +1 -10
  24. data/public/javascripts/builder.js +136 -0
  25. data/public/javascripts/controls.js +486 -354
  26. data/public/javascripts/dragdrop.js +82 -52
  27. data/public/javascripts/effects.js +398 -364
  28. data/public/javascripts/prototype.js +2764 -1095
  29. data/public/javascripts/scriptaculous.js +58 -0
  30. data/public/javascripts/slider.js +275 -0
  31. data/public/javascripts/sound.js +55 -0
  32. data/public/javascripts/unittest.js +568 -0
  33. data/test/functional/backlogs_controller_test.rb +1 -1
  34. data/test/functional/customers_controller_test.rb +93 -0
  35. data/test/functional/tasks_controller_test.rb +4 -8
  36. data/test/functional/work_accounts_controller_test.rb +1 -1
  37. data/test/performance/test_threaded.rb +40 -31
  38. data/test/test_helper.rb +4 -2
  39. metadata +20 -3
  40. data/app/views/tasks/finish_ajax.rjs +0 -25
@@ -2,7 +2,7 @@
2
2
  <th/>
3
3
  <th align="center"><%=active ? '#' : l(:resolution_abr)%></th>
4
4
  <th><%=l :task %></th>
5
- <th><%=l :start if backlog.work_account && backlog.work_account.track_times? && track_times %></th>
5
+ <th><%=l :start if backlog.work_account && backlog.work_account.track_times? %></th>
6
6
  <th/>
7
7
  <th><%=l :done if backlog.track_done? %></th>
8
8
  <th width="*"><%=l :todo if backlog.track_todo? %></th>
@@ -33,7 +33,12 @@
33
33
 
34
34
  <% if @task.enable_customer? %>
35
35
  <p><label for="task_customer"><%=l :customer%></label><br/>
36
- <%= text_field 'task', 'customer', :size => 64 %></p>
36
+ <%=select 'task', 'customer_id', [['', '']] + @customers.map{|c| [c.name, c.id]} %>
37
+ <% if @task.customer %>
38
+ <%=image_detour_to('customer.png', "#{l(:customer)} #{@task.customer.name}", {:controller => 'customers', :action => :edit, :id => @task.customer}, {:class => 'image-submit', :style => 'vertical-align: bottom'}) %>
39
+ <% end %>
40
+ <%=detour_to l(:new_customer), :controller => 'customers', :action => :new %>
41
+ </p>
37
42
  <% end %>
38
43
 
39
44
  <% if @task.track_todo? %>
@@ -69,7 +69,7 @@
69
69
  <% end -%>
70
70
  <% end -%>
71
71
  <% if (not @task.track_times?) && !@task.work_started? && (@task.period.nil? || @task.period.active?) %>
72
- <%= image_link_to_remote('checkmark.png', l(:complete), {:controller => 'tasks', :action => :finish_ajax, :id => @task}, nil, true)%>
72
+ <%= image_link_to_remote('checkmark.png', l(:complete), {:controller => 'tasks', :action => :finish, :id => @task}, nil, true)%>
73
73
  <% end -%>
74
74
  <% end -%>
75
75
  <% end -%>
@@ -79,8 +79,8 @@
79
79
  <% if @task.active? %>
80
80
  <% if @task.loggable? %>
81
81
  <% unless @task.work_started? %>
82
- <%= image_detour_to('arrow_right.png', l(:move_to_next_period), {:controller => 'tasks', :action => :move_to_next_period, :id => @task}, nil, true)%>
83
- <%= image_detour_to('ernes_stop.png', l(:abort), {:controller => 'tasks', :action => :abort, :id => @task}, nil, true)%>
82
+ <%=image_link_to_remote('arrow_right.png', l(:move_to_next_period), {:controller => 'tasks', :action => :move_to_next_period, :id => @task}, nil, true)%>
83
+ <%=image_link_to_remote('ernes_stop.png', l(:abort), {:controller => 'tasks', :action => :abort, :id => @task}, nil, true)%>
84
84
  <% end %>
85
85
  <% end %>
86
86
  <% elsif (@task.period.nil? || (not @task.period.passed?)) && @task.leaf? %>
@@ -0,0 +1,4 @@
1
+ display_notice(page)
2
+ remove_active_task(page)
3
+ add_finished_task(page)
4
+ update_burn_down_chart(page)
@@ -0,0 +1,4 @@
1
+ display_notice(page)
2
+ remove_active_task(page)
3
+ add_finished_task(page)
4
+ update_burn_down_chart(page)
@@ -1,26 +1,6 @@
1
- page.replace :notice, :partial => '/layouts/notice'
2
- page.visual_effect(:appear, :notice)
1
+ display_notice(page)
3
2
 
4
- if @task.lower_item && @task.lower_item.backlog == @task.backlog
5
- page.select('#active_tasks tr').first.remove
6
- page.select('#active_tasks tr').first.remove
7
- end
3
+ remove_finished_task(page)
4
+ add_active_task(page)
8
5
 
9
- page.visual_effect :fade, "task_#{@task.id}"
10
-
11
- page.remove "task_#{@task.id}"
12
-
13
- if @last_finished
14
- page.select('#completed_tasks tr').first.remove
15
- end
16
-
17
- page.insert_html :top, :active_tasks, :partial => '/tasks/task', :locals => { :task => @task, :i => 1, :active => true, :highlight_task => false, :update => :spotlight, :hidden => true }
18
- page[:no_tasks_message].hide
19
-
20
- page.insert_html :top, :active_tasks, :partial => '/tasks/fields_header', :locals => {:backlog => @task.backlog}
21
- page.insert_html :top, :active_tasks, :partial => '/tasks/backlog_header', :locals => {:backlog => @task.backlog}
22
- page.visual_effect :blind_down, "active_tasks"
23
- page.visual_effect :appear, "task_#{@task.id}"
24
-
25
- page.visual_effect :appear, "task_#{@task.id}"
26
- page['burn_down_chart'].src = url_for(:controller => 'periods', :action => :burn_down_chart_thumbnail, :id => @task.period_id, :rnd => rand)
6
+ update_burn_down_chart(page)
data/config/boot.rb CHANGED
@@ -1,15 +1,6 @@
1
1
  # Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb
2
2
 
3
- unless defined?(RAILS_ROOT)
4
- root_path = File.join(File.dirname(__FILE__), '..')
5
-
6
- unless RUBY_PLATFORM =~ /(:?mswin|mingw)/
7
- require 'pathname'
8
- root_path = Pathname.new(root_path).cleanpath(true).to_s
9
- end
10
-
11
- RAILS_ROOT = root_path
12
- end
3
+ RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
13
4
 
14
5
  unless defined?(Rails::Initializer)
15
6
  if File.directory?("#{RAILS_ROOT}/vendor/rails")
@@ -0,0 +1,136 @@
1
+ // script.aculo.us builder.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007
2
+
3
+ // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+ //
5
+ // script.aculo.us is freely distributable under the terms of an MIT-style license.
6
+ // For details, see the script.aculo.us web site: http://script.aculo.us/
7
+
8
+ var Builder = {
9
+ NODEMAP: {
10
+ AREA: 'map',
11
+ CAPTION: 'table',
12
+ COL: 'table',
13
+ COLGROUP: 'table',
14
+ LEGEND: 'fieldset',
15
+ OPTGROUP: 'select',
16
+ OPTION: 'select',
17
+ PARAM: 'object',
18
+ TBODY: 'table',
19
+ TD: 'table',
20
+ TFOOT: 'table',
21
+ TH: 'table',
22
+ THEAD: 'table',
23
+ TR: 'table'
24
+ },
25
+ // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
26
+ // due to a Firefox bug
27
+ node: function(elementName) {
28
+ elementName = elementName.toUpperCase();
29
+
30
+ // try innerHTML approach
31
+ var parentTag = this.NODEMAP[elementName] || 'div';
32
+ var parentElement = document.createElement(parentTag);
33
+ try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
34
+ parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
35
+ } catch(e) {}
36
+ var element = parentElement.firstChild || null;
37
+
38
+ // see if browser added wrapping tags
39
+ if(element && (element.tagName.toUpperCase() != elementName))
40
+ element = element.getElementsByTagName(elementName)[0];
41
+
42
+ // fallback to createElement approach
43
+ if(!element) element = document.createElement(elementName);
44
+
45
+ // abort if nothing could be created
46
+ if(!element) return;
47
+
48
+ // attributes (or text)
49
+ if(arguments[1])
50
+ if(this._isStringOrNumber(arguments[1]) ||
51
+ (arguments[1] instanceof Array) ||
52
+ arguments[1].tagName) {
53
+ this._children(element, arguments[1]);
54
+ } else {
55
+ var attrs = this._attributes(arguments[1]);
56
+ if(attrs.length) {
57
+ try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
58
+ parentElement.innerHTML = "<" +elementName + " " +
59
+ attrs + "></" + elementName + ">";
60
+ } catch(e) {}
61
+ element = parentElement.firstChild || null;
62
+ // workaround firefox 1.0.X bug
63
+ if(!element) {
64
+ element = document.createElement(elementName);
65
+ for(attr in arguments[1])
66
+ element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
67
+ }
68
+ if(element.tagName.toUpperCase() != elementName)
69
+ element = parentElement.getElementsByTagName(elementName)[0];
70
+ }
71
+ }
72
+
73
+ // text, or array of children
74
+ if(arguments[2])
75
+ this._children(element, arguments[2]);
76
+
77
+ return element;
78
+ },
79
+ _text: function(text) {
80
+ return document.createTextNode(text);
81
+ },
82
+
83
+ ATTR_MAP: {
84
+ 'className': 'class',
85
+ 'htmlFor': 'for'
86
+ },
87
+
88
+ _attributes: function(attributes) {
89
+ var attrs = [];
90
+ for(attribute in attributes)
91
+ attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
92
+ '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
93
+ return attrs.join(" ");
94
+ },
95
+ _children: function(element, children) {
96
+ if(children.tagName) {
97
+ element.appendChild(children);
98
+ return;
99
+ }
100
+ if(typeof children=='object') { // array can hold nodes and text
101
+ children.flatten().each( function(e) {
102
+ if(typeof e=='object')
103
+ element.appendChild(e)
104
+ else
105
+ if(Builder._isStringOrNumber(e))
106
+ element.appendChild(Builder._text(e));
107
+ });
108
+ } else
109
+ if(Builder._isStringOrNumber(children))
110
+ element.appendChild(Builder._text(children));
111
+ },
112
+ _isStringOrNumber: function(param) {
113
+ return(typeof param=='string' || typeof param=='number');
114
+ },
115
+ build: function(html) {
116
+ var element = this.node('div');
117
+ $(element).update(html.strip());
118
+ return element.down();
119
+ },
120
+ dump: function(scope) {
121
+ if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope
122
+
123
+ var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
124
+ "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
125
+ "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
126
+ "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
127
+ "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
128
+ "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
129
+
130
+ tags.each( function(tag){
131
+ scope[tag] = function() {
132
+ return Builder.node.apply(Builder, [tag].concat($A(arguments)));
133
+ }
134
+ });
135
+ }
136
+ }
@@ -1,6 +1,8 @@
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
+ // script.aculo.us controls.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007
2
+
3
+ // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+ // (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
5
+ // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
4
6
  // Contributors:
5
7
  // Richard Livsey
6
8
  // Rahul Bhargava
@@ -37,22 +39,23 @@
37
39
  if(typeof Effect == 'undefined')
38
40
  throw("controls.js requires including script.aculo.us' effects.js library");
39
41
 
40
- var Autocompleter = {}
41
- Autocompleter.Base = function() {};
42
- Autocompleter.Base.prototype = {
42
+ var Autocompleter = { }
43
+ Autocompleter.Base = Class.create({
43
44
  baseInitialize: function(element, update, options) {
44
- this.element = $(element);
45
+ element = $(element)
46
+ this.element = element;
45
47
  this.update = $(update);
46
48
  this.hasFocus = false;
47
49
  this.changed = false;
48
50
  this.active = false;
49
51
  this.index = 0;
50
52
  this.entryCount = 0;
53
+ this.oldElementValue = this.element.value;
51
54
 
52
55
  if(this.setOptions)
53
56
  this.setOptions(options);
54
57
  else
55
- this.options = options || {};
58
+ this.options = options || { };
56
59
 
57
60
  this.options.paramName = this.options.paramName || this.element.name;
58
61
  this.options.tokens = this.options.tokens || [];
@@ -74,6 +77,9 @@ Autocompleter.Base.prototype = {
74
77
 
75
78
  if(typeof(this.options.tokens) == 'string')
76
79
  this.options.tokens = new Array(this.options.tokens);
80
+ // Force carriage returns as token delimiters anyway
81
+ if (!this.options.tokens.include('\n'))
82
+ this.options.tokens.push('\n');
77
83
 
78
84
  this.observer = null;
79
85
 
@@ -81,15 +87,14 @@ Autocompleter.Base.prototype = {
81
87
 
82
88
  Element.hide(this.update);
83
89
 
84
- Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
85
- Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
90
+ Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
91
+ Event.observe(this.element, 'keypress', this.onKeyPress.bindAsEventListener(this));
86
92
  },
87
93
 
88
94
  show: function() {
89
95
  if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
90
96
  if(!this.iefix &&
91
- (navigator.appVersion.indexOf('MSIE')>0) &&
92
- (navigator.userAgent.indexOf('Opera')<0) &&
97
+ (Prototype.Browser.IE) &&
93
98
  (Element.getStyle(this.update, 'position')=='absolute')) {
94
99
  new Insertion.After(this.update,
95
100
  '<iframe id="' + this.update.id + '_iefix" '+
@@ -139,17 +144,17 @@ Autocompleter.Base.prototype = {
139
144
  case Event.KEY_UP:
140
145
  this.markPrevious();
141
146
  this.render();
142
- if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
147
+ if(Prototype.Browser.WebKit) Event.stop(event);
143
148
  return;
144
149
  case Event.KEY_DOWN:
145
150
  this.markNext();
146
151
  this.render();
147
- if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
152
+ if(Prototype.Browser.WebKit) Event.stop(event);
148
153
  return;
149
154
  }
150
155
  else
151
156
  if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
152
- (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
157
+ (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
153
158
 
154
159
  this.changed = true;
155
160
  this.hasFocus = true;
@@ -195,7 +200,6 @@ Autocompleter.Base.prototype = {
195
200
  this.index==i ?
196
201
  Element.addClassName(this.getEntry(i),"selected") :
197
202
  Element.removeClassName(this.getEntry(i),"selected");
198
-
199
203
  if(this.hasFocus) {
200
204
  this.show();
201
205
  this.active = true;
@@ -238,21 +242,22 @@ Autocompleter.Base.prototype = {
238
242
  }
239
243
  var value = '';
240
244
  if (this.options.select) {
241
- var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
245
+ var nodes = $(selectedElement).select('.' + this.options.select) || [];
242
246
  if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
243
247
  } else
244
248
  value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
245
249
 
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+/);
250
+ var bounds = this.getTokenBounds();
251
+ if (bounds[0] != -1) {
252
+ var newValue = this.element.value.substr(0, bounds[0]);
253
+ var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
250
254
  if (whitespace)
251
255
  newValue += whitespace[0];
252
- this.element.value = newValue + value;
256
+ this.element.value = newValue + value + this.element.value.substr(bounds[1]);
253
257
  } else {
254
258
  this.element.value = value;
255
259
  }
260
+ this.oldElementValue = this.element.value;
256
261
  this.element.focus();
257
262
 
258
263
  if (this.options.afterUpdateElement)
@@ -296,39 +301,48 @@ Autocompleter.Base.prototype = {
296
301
 
297
302
  onObserverEvent: function() {
298
303
  this.changed = false;
304
+ this.tokenBounds = null;
299
305
  if(this.getToken().length>=this.options.minChars) {
300
- this.startIndicator();
301
306
  this.getUpdatedChoices();
302
307
  } else {
303
308
  this.active = false;
304
309
  this.hide();
305
310
  }
311
+ this.oldElementValue = this.element.value;
306
312
  },
307
313
 
308
314
  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;
315
+ var bounds = this.getTokenBounds();
316
+ return this.element.value.substring(bounds[0], bounds[1]).strip();
317
+ },
318
+
319
+ getTokenBounds: function() {
320
+ if (null != this.tokenBounds) return this.tokenBounds;
321
+ var value = this.element.value;
322
+ if (value.strip().empty()) return [-1, 0];
323
+ var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
324
+ var offset = (diff == this.oldElementValue.length ? 1 : 0);
325
+ var prevTokenPos = -1, nextTokenPos = value.length;
326
+ var tp;
327
+ for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
328
+ tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
329
+ if (tp > prevTokenPos) prevTokenPos = tp;
330
+ tp = value.indexOf(this.options.tokens[index], diff + offset);
331
+ if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
325
332
  }
326
- return lastTokenPos;
333
+ return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
327
334
  }
328
- }
335
+ });
336
+
337
+ Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
338
+ var boundary = Math.min(newS.length, oldS.length);
339
+ for (var index = 0; index < boundary; ++index)
340
+ if (newS[index] != oldS[index])
341
+ return index;
342
+ return boundary;
343
+ };
329
344
 
330
- Ajax.Autocompleter = Class.create();
331
- Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
345
+ Ajax.Autocompleter = Class.create(Autocompleter.Base, {
332
346
  initialize: function(element, update, url, options) {
333
347
  this.baseInitialize(element, update, options);
334
348
  this.options.asynchronous = true;
@@ -338,7 +352,9 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
338
352
  },
339
353
 
340
354
  getUpdatedChoices: function() {
341
- entry = encodeURIComponent(this.options.paramName) + '=' +
355
+ this.startIndicator();
356
+
357
+ var entry = encodeURIComponent(this.options.paramName) + '=' +
342
358
  encodeURIComponent(this.getToken());
343
359
 
344
360
  this.options.parameters = this.options.callback ?
@@ -346,14 +362,13 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
346
362
 
347
363
  if(this.options.defaultParams)
348
364
  this.options.parameters += '&' + this.options.defaultParams;
349
-
365
+
350
366
  new Ajax.Request(this.url, this.options);
351
367
  },
352
368
 
353
369
  onComplete: function(request) {
354
370
  this.updateChoices(request.responseText);
355
371
  }
356
-
357
372
  });
358
373
 
359
374
  // The local array autocompleter. Used when you'd prefer to
@@ -391,8 +406,7 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
391
406
  // In that case, the other options above will not apply unless
392
407
  // you support them.
393
408
 
394
- Autocompleter.Local = Class.create();
395
- Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
409
+ Autocompleter.Local = Class.create(Autocompleter.Base, {
396
410
  initialize: function(element, update, array, options) {
397
411
  this.baseInitialize(element, update, options);
398
412
  this.options.array = array;
@@ -448,13 +462,12 @@ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
448
462
  ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
449
463
  return "<ul>" + ret.join('') + "</ul>";
450
464
  }
451
- }, options || {});
465
+ }, options || { });
452
466
  }
453
467
  });
454
468
 
455
- // AJAX in-place editor
456
- //
457
- // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
469
+ // AJAX in-place editor and collection editor
470
+ // Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
458
471
 
459
472
  // Use this if you notice weird scrolling problems on some browsers,
460
473
  // the DOM might be a bit confused when this gets called so do this
@@ -465,353 +478,472 @@ Field.scrollFreeActivate = function(field) {
465
478
  }, 1);
466
479
  }
467
480
 
468
- Ajax.InPlaceEditor = Class.create();
469
- Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
470
- Ajax.InPlaceEditor.prototype = {
481
+ Ajax.InPlaceEditor = Class.create({
471
482
  initialize: function(element, url, options) {
472
483
  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
- }
484
+ this.element = element = $(element);
485
+ this.prepareOptions();
486
+ this._controls = { };
487
+ arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
488
+ Object.extend(this.options, options || { });
489
+ if (!this.options.formId && this.element.id) {
490
+ this.options.formId = this.element.id + '-inplaceeditor';
491
+ if ($(this.options.formId))
492
+ this.options.formId = '';
513
493
  }
514
-
515
- if (this.options.externalControl) {
494
+ if (this.options.externalControl)
516
495
  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
-
496
+ if (!this.options.externalControl)
497
+ this.options.externalControlOnly = false;
498
+ this._originalBackground = this.element.getStyle('background-color') || 'transparent';
524
499
  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);
547
- 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);
500
+ this._boundCancelHandler = this.handleFormCancellation.bind(this);
501
+ this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
502
+ this._boundFailureHandler = this.handleAJAXFailure.bind(this);
503
+ this._boundSubmitHandler = this.handleFormSubmission.bind(this);
504
+ this._boundWrapperHandler = this.wrapUp.bind(this);
505
+ this.registerListeners();
506
+ },
507
+ checkForEscapeOrReturn: function(e) {
508
+ if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
509
+ if (Event.KEY_ESC == e.keyCode)
510
+ this.handleFormCancellation(e);
511
+ else if (Event.KEY_RETURN == e.keyCode)
512
+ this.handleFormSubmission(e);
513
+ },
514
+ createControl: function(mode, handler, extraClasses) {
515
+ var control = this.options[mode + 'Control'];
516
+ var text = this.options[mode + 'Text'];
517
+ if ('button' == control) {
518
+ var btn = document.createElement('input');
519
+ btn.type = 'submit';
520
+ btn.value = text;
521
+ btn.className = 'editor_' + mode + '_button';
522
+ if ('cancel' == mode)
523
+ btn.onclick = this._boundCancelHandler;
524
+ this._form.appendChild(btn);
525
+ this._controls[mode] = btn;
526
+ } else if ('link' == control) {
527
+ var link = document.createElement('a');
528
+ link.href = '#';
529
+ link.appendChild(document.createTextNode(text));
530
+ link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
531
+ link.className = 'editor_' + mode + '_link';
532
+ if (extraClasses)
533
+ link.className += ' ' + extraClasses;
534
+ this._form.appendChild(link);
535
+ this._controls[mode] = link;
584
536
  }
585
537
  },
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
538
  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';
539
+ var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
540
+ var fld;
541
+ if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
542
+ fld = document.createElement('input');
543
+ fld.type = 'text';
612
544
  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;
545
+ if (0 < size) fld.size = size;
617
546
  } 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;
547
+ fld = document.createElement('textarea');
548
+ fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
549
+ fld.cols = this.options.cols || 40;
629
550
  }
630
-
631
- if(this.options.loadTextURL) {
551
+ fld.name = this.options.paramName;
552
+ fld.value = text; // No HTML breaks conversion anymore
553
+ fld.className = 'editor_field';
554
+ if (this.options.submitOnBlur)
555
+ fld.onblur = this._boundSubmitHandler;
556
+ this._controls.editor = fld;
557
+ if (this.options.loadTextURL)
632
558
  this.loadExternalText();
633
- }
634
- this.form.appendChild(this.editField);
559
+ this._form.appendChild(this._controls.editor);
560
+ },
561
+ createForm: function() {
562
+ var ipe = this;
563
+ function addText(mode, condition) {
564
+ var text = ipe.options['text' + mode + 'Controls'];
565
+ if (!text || condition === false) return;
566
+ ipe._form.appendChild(document.createTextNode(text));
567
+ };
568
+ this._form = $(document.createElement('form'));
569
+ this._form.id = this.options.formId;
570
+ this._form.addClassName(this.options.formClassName);
571
+ this._form.onsubmit = this._boundSubmitHandler;
572
+ this.createEditField();
573
+ if ('textarea' == this._controls.editor.tagName.toLowerCase())
574
+ this._form.appendChild(document.createElement('br'));
575
+ if (this.options.onFormCustomization)
576
+ this.options.onFormCustomization(this, this._form);
577
+ addText('Before', this.options.okControl || this.options.cancelControl);
578
+ this.createControl('ok', this._boundSubmitHandler);
579
+ addText('Between', this.options.okControl && this.options.cancelControl);
580
+ this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
581
+ addText('After', this.options.okControl || this.options.cancelControl);
582
+ },
583
+ destroy: function() {
584
+ if (this._oldInnerHTML)
585
+ this.element.innerHTML = this._oldInnerHTML;
586
+ this.leaveEditMode();
587
+ this.unregisterListeners();
588
+ },
589
+ enterEditMode: function(e) {
590
+ if (this._saving || this._editing) return;
591
+ this._editing = true;
592
+ this.triggerCallback('onEnterEditMode');
593
+ if (this.options.externalControl)
594
+ this.options.externalControl.hide();
595
+ this.element.hide();
596
+ this.createForm();
597
+ this.element.parentNode.insertBefore(this._form, this.element);
598
+ if (!this.options.loadTextURL)
599
+ this.postProcessEditField();
600
+ if (e) Event.stop(e);
601
+ },
602
+ enterHover: function(e) {
603
+ if (this.options.hoverClassName)
604
+ this.element.addClassName(this.options.hoverClassName);
605
+ if (this._saving) return;
606
+ this.triggerCallback('onEnterHover');
635
607
  },
636
608
  getText: function() {
637
609
  return this.element.innerHTML;
638
610
  },
639
- 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;
611
+ handleAJAXFailure: function(transport) {
612
+ this.triggerCallback('onFailure', transport);
613
+ if (this._oldInnerHTML) {
614
+ this.element.innerHTML = this._oldInnerHTML;
615
+ this._oldInnerHTML = null;
666
616
  }
667
- return false;
668
617
  },
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]);
618
+ handleFormCancellation: function(e) {
619
+ this.wrapUp();
620
+ if (e) Event.stop(e);
621
+ },
622
+ handleFormSubmission: function(e) {
623
+ var form = this._form;
624
+ var value = $F(this._controls.editor);
625
+ this.prepareSubmission();
626
+ var params = this.options.callback(form, value) || '';
627
+ if (Object.isString(params))
628
+ params = params.toQueryParams();
629
+ params.editorId = this.element.id;
630
+ if (this.options.htmlResponse) {
631
+ var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
632
+ Object.extend(options, {
633
+ parameters: params,
634
+ onComplete: this._boundWrapperHandler,
635
+ onFailure: this._boundFailureHandler
636
+ });
637
+ new Ajax.Updater({ success: this.element }, this.url, options);
638
+ } else {
639
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
640
+ Object.extend(options, {
641
+ parameters: params,
642
+ onComplete: this._boundWrapperHandler,
643
+ onFailure: this._boundFailureHandler
644
+ });
645
+ new Ajax.Request(this.url, options);
702
646
  }
703
- return false;
647
+ if (e) Event.stop(e);
648
+ },
649
+ leaveEditMode: function() {
650
+ this.element.removeClassName(this.options.savingClassName);
651
+ this.removeForm();
652
+ this.leaveHover();
653
+ this.element.style.backgroundColor = this._originalBackground;
654
+ this.element.show();
655
+ if (this.options.externalControl)
656
+ this.options.externalControl.show();
657
+ this._saving = false;
658
+ this._editing = false;
659
+ this._oldInnerHTML = null;
660
+ this.triggerCallback('onLeaveEditMode');
661
+ },
662
+ leaveHover: function(e) {
663
+ if (this.options.hoverClassName)
664
+ this.element.removeClassName(this.options.hoverClassName);
665
+ if (this._saving) return;
666
+ this.triggerCallback('onLeaveHover');
704
667
  },
705
- onLoading: function() {
706
- this.saving = true;
668
+ loadExternalText: function() {
669
+ this._form.addClassName(this.options.loadingClassName);
670
+ this._controls.editor.disabled = true;
671
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
672
+ Object.extend(options, {
673
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
674
+ onComplete: Prototype.emptyFunction,
675
+ onSuccess: function(transport) {
676
+ this._form.removeClassName(this.options.loadingClassName);
677
+ var text = transport.responseText;
678
+ if (this.options.stripLoadedTextTags)
679
+ text = text.stripTags();
680
+ this._controls.editor.value = text;
681
+ this._controls.editor.disabled = false;
682
+ this.postProcessEditField();
683
+ }.bind(this),
684
+ onFailure: this._boundFailureHandler
685
+ });
686
+ new Ajax.Request(this.options.loadTextURL, options);
687
+ },
688
+ postProcessEditField: function() {
689
+ var fpc = this.options.fieldPostCreation;
690
+ if (fpc)
691
+ $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
692
+ },
693
+ prepareOptions: function() {
694
+ this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
695
+ Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
696
+ [this._extraDefaultOptions].flatten().compact().each(function(defs) {
697
+ Object.extend(this.options, defs);
698
+ }.bind(this));
699
+ },
700
+ prepareSubmission: function() {
701
+ this._saving = true;
707
702
  this.removeForm();
708
703
  this.leaveHover();
709
704
  this.showSaving();
710
705
  },
706
+ registerListeners: function() {
707
+ this._listeners = { };
708
+ var listener;
709
+ $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
710
+ listener = this[pair.value].bind(this);
711
+ this._listeners[pair.key] = listener;
712
+ if (!this.options.externalControlOnly)
713
+ this.element.observe(pair.key, listener);
714
+ if (this.options.externalControl)
715
+ this.options.externalControl.observe(pair.key, listener);
716
+ }.bind(this));
717
+ },
718
+ removeForm: function() {
719
+ if (!this._form) return;
720
+ this._form.remove();
721
+ this._form = null;
722
+ this._controls = { };
723
+ },
711
724
  showSaving: function() {
712
- this.oldInnerHTML = this.element.innerHTML;
725
+ this._oldInnerHTML = this.element.innerHTML;
713
726
  this.element.innerHTML = this.options.savingText;
714
- Element.addClassName(this.element, this.options.savingClassName);
715
- this.element.style.backgroundColor = this.originalBackground;
716
- Element.show(this.element);
727
+ this.element.addClassName(this.options.savingClassName);
728
+ this.element.style.backgroundColor = this._originalBackground;
729
+ this.element.show();
717
730
  },
718
- removeForm: function() {
719
- if(this.form) {
720
- if (this.form.parentNode) Element.remove(this.form);
721
- this.form = null;
731
+ triggerCallback: function(cbName, arg) {
732
+ if ('function' == typeof this.options[cbName]) {
733
+ this.options[cbName](this, arg);
722
734
  }
723
735
  },
724
- enterHover: function() {
725
- if (this.saving) return;
726
- this.element.style.backgroundColor = this.options.highlightcolor;
727
- if (this.effect) {
728
- this.effect.cancel();
729
- }
730
- Element.addClassName(this.element, this.options.hoverClassName)
736
+ unregisterListeners: function() {
737
+ $H(this._listeners).each(function(pair) {
738
+ if (!this.options.externalControlOnly)
739
+ this.element.stopObserving(pair.key, pair.value);
740
+ if (this.options.externalControl)
741
+ this.options.externalControl.stopObserving(pair.key, pair.value);
742
+ }.bind(this));
731
743
  },
732
- leaveHover: function() {
733
- if (this.options.backgroundColor) {
734
- this.element.style.backgroundColor = this.oldBackground;
735
- }
736
- Element.removeClassName(this.element, this.options.hoverClassName)
737
- if (this.saving) return;
738
- this.effect = new Effect.Highlight(this.element, {
739
- startcolor: this.options.highlightcolor,
740
- endcolor: this.options.highlightendcolor,
741
- restorecolor: this.originalBackground
744
+ wrapUp: function(transport) {
745
+ this.leaveEditMode();
746
+ // Can't use triggerCallback due to backward compatibility: requires
747
+ // binding + direct element
748
+ this._boundComplete(transport, this.element);
749
+ }
750
+ });
751
+
752
+ Object.extend(Ajax.InPlaceEditor.prototype, {
753
+ dispose: Ajax.InPlaceEditor.prototype.destroy
754
+ });
755
+
756
+ Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
757
+ initialize: function($super, element, url, options) {
758
+ this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
759
+ $super(element, url, options);
760
+ },
761
+
762
+ createEditField: function() {
763
+ var list = document.createElement('select');
764
+ list.name = this.options.paramName;
765
+ list.size = 1;
766
+ this._controls.editor = list;
767
+ this._collection = this.options.collection || [];
768
+ if (this.options.loadCollectionURL)
769
+ this.loadCollection();
770
+ else
771
+ this.checkForExternalText();
772
+ this._form.appendChild(this._controls.editor);
773
+ },
774
+
775
+ loadCollection: function() {
776
+ this._form.addClassName(this.options.loadingClassName);
777
+ this.showLoadingText(this.options.loadingCollectionText);
778
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
779
+ Object.extend(options, {
780
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
781
+ onComplete: Prototype.emptyFunction,
782
+ onSuccess: function(transport) {
783
+ var js = transport.responseText.strip();
784
+ if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
785
+ throw 'Server returned an invalid collection representation.';
786
+ this._collection = eval(js);
787
+ this.checkForExternalText();
788
+ }.bind(this),
789
+ onFailure: this.onFailure
742
790
  });
791
+ new Ajax.Request(this.options.loadCollectionURL, options);
743
792
  },
744
- leaveEditMode: function() {
745
- Element.removeClassName(this.element, this.options.savingClassName);
746
- this.removeForm();
747
- this.leaveHover();
748
- this.element.style.backgroundColor = this.originalBackground;
749
- Element.show(this.element);
750
- if (this.options.externalControl) {
751
- Element.show(this.options.externalControl);
793
+
794
+ showLoadingText: function(text) {
795
+ this._controls.editor.disabled = true;
796
+ var tempOption = this._controls.editor.firstChild;
797
+ if (!tempOption) {
798
+ tempOption = document.createElement('option');
799
+ tempOption.value = '';
800
+ this._controls.editor.appendChild(tempOption);
801
+ tempOption.selected = true;
752
802
  }
753
- this.editing = false;
754
- this.saving = false;
755
- this.oldInnerHTML = null;
756
- this.onLeaveEditMode();
803
+ tempOption.update((text || '').stripScripts().stripTags());
757
804
  },
758
- onComplete: function(transport) {
759
- this.leaveEditMode();
760
- this.options.onComplete.bind(this)(transport, this.element);
805
+
806
+ checkForExternalText: function() {
807
+ this._text = this.getText();
808
+ if (this.options.loadTextURL)
809
+ this.loadExternalText();
810
+ else
811
+ this.buildOptionList();
761
812
  },
762
- onEnterEditMode: function() {},
763
- onLeaveEditMode: function() {},
764
- dispose: function() {
765
- if (this.oldInnerHTML) {
766
- this.element.innerHTML = this.oldInnerHTML;
767
- }
768
- this.leaveEditMode();
769
- Event.stopObserving(this.element, 'click', this.onclickListener);
770
- Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
771
- Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
772
- if (this.options.externalControl) {
773
- Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
774
- Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
775
- Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
776
- }
813
+
814
+ loadExternalText: function() {
815
+ this.showLoadingText(this.options.loadingText);
816
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
817
+ Object.extend(options, {
818
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
819
+ onComplete: Prototype.emptyFunction,
820
+ onSuccess: function(transport) {
821
+ this._text = transport.responseText.strip();
822
+ this.buildOptionList();
823
+ }.bind(this),
824
+ onFailure: this.onFailure
825
+ });
826
+ new Ajax.Request(this.options.loadTextURL, options);
827
+ },
828
+
829
+ buildOptionList: function() {
830
+ this._form.removeClassName(this.options.loadingClassName);
831
+ this._collection = this._collection.map(function(entry) {
832
+ return 2 === entry.length ? entry : [entry, entry].flatten();
833
+ });
834
+ var marker = ('value' in this.options) ? this.options.value : this._text;
835
+ var textFound = this._collection.any(function(entry) {
836
+ return entry[0] == marker;
837
+ }.bind(this));
838
+ this._controls.editor.update('');
839
+ var option;
840
+ this._collection.each(function(entry, index) {
841
+ option = document.createElement('option');
842
+ option.value = entry[0];
843
+ option.selected = textFound ? entry[0] == marker : 0 == index;
844
+ option.appendChild(document.createTextNode(entry[1]));
845
+ this._controls.editor.appendChild(option);
846
+ }.bind(this));
847
+ this._controls.editor.disabled = false;
848
+ Field.scrollFreeActivate(this._controls.editor);
777
849
  }
778
- };
850
+ });
779
851
 
780
- Ajax.InPlaceCollectionEditor = Class.create();
781
- Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
782
- Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
783
- createEditField: function() {
784
- if (!this.cached_selectTag) {
785
- var selectTag = document.createElement("select");
786
- var collection = this.options.collection || [];
787
- var optionTag;
788
- collection.each(function(e,i) {
789
- optionTag = document.createElement("option");
790
- optionTag.value = (e instanceof Array) ? e[0] : e;
791
- if((typeof this.options.value == 'undefined') &&
792
- ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
793
- if(this.options.value==optionTag.value) optionTag.selected = true;
794
- optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
795
- selectTag.appendChild(optionTag);
796
- }.bind(this));
797
- this.cached_selectTag = selectTag;
798
- }
852
+ //**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
853
+ //**** This only exists for a while, in order to let ****
854
+ //**** users adapt to the new API. Read up on the new ****
855
+ //**** API and convert your code to it ASAP! ****
856
+
857
+ Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
858
+ if (!options) return;
859
+ function fallback(name, expr) {
860
+ if (name in options || expr === undefined) return;
861
+ options[name] = expr;
862
+ };
863
+ fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
864
+ options.cancelLink == options.cancelButton == false ? false : undefined)));
865
+ fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
866
+ options.okLink == options.okButton == false ? false : undefined)));
867
+ fallback('highlightColor', options.highlightcolor);
868
+ fallback('highlightEndColor', options.highlightendcolor);
869
+ };
799
870
 
800
- this.editField = this.cached_selectTag;
801
- if(this.options.loadTextURL) this.loadExternalText();
802
- this.form.appendChild(this.editField);
803
- this.options.callback = function(form, value) {
804
- return "value=" + encodeURIComponent(value);
871
+ Object.extend(Ajax.InPlaceEditor, {
872
+ DefaultOptions: {
873
+ ajaxOptions: { },
874
+ autoRows: 3, // Use when multi-line w/ rows == 1
875
+ cancelControl: 'link', // 'link'|'button'|false
876
+ cancelText: 'cancel',
877
+ clickToEditText: 'Click to edit',
878
+ externalControl: null, // id|elt
879
+ externalControlOnly: false,
880
+ fieldPostCreation: 'activate', // 'activate'|'focus'|false
881
+ formClassName: 'inplaceeditor-form',
882
+ formId: null, // id|elt
883
+ highlightColor: '#ffff99',
884
+ highlightEndColor: '#ffffff',
885
+ hoverClassName: '',
886
+ htmlResponse: true,
887
+ loadingClassName: 'inplaceeditor-loading',
888
+ loadingText: 'Loading...',
889
+ okControl: 'button', // 'link'|'button'|false
890
+ okText: 'ok',
891
+ paramName: 'value',
892
+ rows: 1, // If 1 and multi-line, uses autoRows
893
+ savingClassName: 'inplaceeditor-saving',
894
+ savingText: 'Saving...',
895
+ size: 0,
896
+ stripLoadedTextTags: false,
897
+ submitOnBlur: false,
898
+ textAfterControls: '',
899
+ textBeforeControls: '',
900
+ textBetweenControls: ''
901
+ },
902
+ DefaultCallbacks: {
903
+ callback: function(form) {
904
+ return Form.serialize(form);
905
+ },
906
+ onComplete: function(transport, element) {
907
+ // For backward compatibility, this one is bound to the IPE, and passes
908
+ // the element directly. It was too often customized, so we don't break it.
909
+ new Effect.Highlight(element, {
910
+ startcolor: this.options.highlightColor, keepBackgroundImage: true });
911
+ },
912
+ onEnterEditMode: null,
913
+ onEnterHover: function(ipe) {
914
+ ipe.element.style.backgroundColor = ipe.options.highlightColor;
915
+ if (ipe._effect)
916
+ ipe._effect.cancel();
917
+ },
918
+ onFailure: function(transport, ipe) {
919
+ alert('Error communication with the server: ' + transport.responseText.stripTags());
920
+ },
921
+ onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
922
+ onLeaveEditMode: null,
923
+ onLeaveHover: function(ipe) {
924
+ ipe._effect = new Effect.Highlight(ipe.element, {
925
+ startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
926
+ restorecolor: ipe._originalBackground, keepBackgroundImage: true
927
+ });
805
928
  }
929
+ },
930
+ Listeners: {
931
+ click: 'enterEditMode',
932
+ keydown: 'checkForEscapeOrReturn',
933
+ mouseover: 'enterHover',
934
+ mouseout: 'leaveHover'
806
935
  }
807
936
  });
808
937
 
938
+ Ajax.InPlaceCollectionEditor.DefaultOptions = {
939
+ loadingCollectionText: 'Loading options...'
940
+ };
941
+
809
942
  // Delayed observer, like Form.Element.Observer,
810
943
  // but waits for delay after last key input
811
944
  // Ideal for live-search fields
812
945
 
813
- Form.Element.DelayedObserver = Class.create();
814
- Form.Element.DelayedObserver.prototype = {
946
+ Form.Element.DelayedObserver = Class.create({
815
947
  initialize: function(element, delay, callback) {
816
948
  this.delay = delay || 0.5;
817
949
  this.element = $(element);
@@ -830,4 +962,4 @@ Form.Element.DelayedObserver.prototype = {
830
962
  this.timer = null;
831
963
  this.callback(this.element, $F(this.element));
832
964
  }
833
- };
965
+ });