Description:
fixed incompatibilities with rail 2.1 git-svn-id: http://theory.cpe.ku.ac.th/grader/web/trunk@276 6386c4cd-e34a-4fa8-8920-d93eb39b512e
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r137:d08ca3e34dfe - - The requested commit is too big and content was truncated. 7 files changed. Show full diff

@@ -13,7 +13,8
13 13 verify :method => :post, :only => [:submit],
14 14 :redirect_to => { :action => :index }
15 15
16 - caches_action :index, :login
16 + # COMMENT OUT, only need when having high load
17 + # caches_action :index, :login
17 18
18 19 def index
19 20 redirect_to :action => 'login'
@@ -1,18 +1,18
1 1 <tr class="info-<%= (problem_counter%2==0) ? "even" : "odd" %>">
2 2 <td>
3 - <%= "#{problem_counter + 1}" %>
3 + <%= "#{problem_counter}" %>
4 4 </td>
5 5 <td>
6 6 <%= "#{problem.full_name} (#{problem.name})" %>
7 7 <%= link_to '[desc]', problem.url, :popup => true if (problem.url!=nil) and (problem.url!='') %>
8 8 </td>
9 9 <td align="center">
10 - <%= @prob_submissions[problem_counter][:count] %>
10 + <%= @prob_submissions[problem_counter-1][:count] %>
11 11 </td>
12 12 <td>
13 13 <%= render :partial => 'submission_short',
14 14 :locals => {
15 - :submission => @prob_submissions[problem_counter][:submission],
15 + :submission => @prob_submissions[problem_counter-1][:submission],
16 16 :problem_name => problem.name }%>
17 17 </td>
18 18 </tr>
@@ -1,45 +1,109
1 - # Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb
1 + # Don't change this file!
2 + # Configure your app in config/environment.rb and config/environments/*.rb
3 +
4 + RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
5 +
6 + module Rails
7 + class << self
8 + def boot!
9 + unless booted?
10 + preinitialize
11 + pick_boot.run
12 + end
13 + end
2 14
3 - unless defined?(RAILS_ROOT)
4 - root_path = File.join(File.dirname(__FILE__), '..')
15 + def booted?
16 + defined? Rails::Initializer
17 + end
18 +
19 + def pick_boot
20 + (vendor_rails? ? VendorBoot : GemBoot).new
21 + end
5 22
6 - unless RUBY_PLATFORM =~ /(:?mswin|mingw)/
7 - require 'pathname'
8 - root_path = Pathname.new(root_path).cleanpath(true).to_s
23 + def vendor_rails?
24 + File.exist?("#{RAILS_ROOT}/vendor/rails")
25 + end
26 +
27 + def preinitialize
28 + load(preinitializer_path) if File.exist?(preinitializer_path)
29 + end
30 +
31 + def preinitializer_path
32 + "#{RAILS_ROOT}/config/preinitializer.rb"
33 + end
9 34 end
10 35
11 - RAILS_ROOT = root_path
12 - end
13 -
14 - unless defined?(Rails::Initializer)
15 - if File.directory?("#{RAILS_ROOT}/vendor/rails")
16 - require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
17 - else
18 - require 'rubygems'
19 -
20 - environment_without_comments = IO.readlines(File.dirname(__FILE__) + '/environment.rb').reject { |l| l =~ /^#/ }.join
21 - environment_without_comments =~ /[^#]RAILS_GEM_VERSION = '([\d.]+)'/
22 - rails_gem_version = $1
36 + class Boot
37 + def run
38 + load_initializer
39 + Rails::Initializer.run(:set_load_path)
40 + end
41 + end
23 42
24 - if version = defined?(RAILS_GEM_VERSION) ? RAILS_GEM_VERSION : rails_gem_version
25 - # Asking for 1.1.6 will give you 1.1.6.5206, if available -- makes it easier to use beta gems
26 - rails_gem = Gem.cache.search('rails', "~>#{version}.0").sort_by { |g| g.version.version }.last
27 -
28 - if rails_gem
29 - gem "rails", "=#{rails_gem.version.version}"
30 - require rails_gem.full_gem_path + '/lib/initializer'
31 - else
32 - STDERR.puts %(Cannot find gem for Rails ~>#{version}.0:
33 - Install the missing gem with 'gem install -v=#{version} rails', or
34 - change environment.rb to define RAILS_GEM_VERSION with your desired version.
35 - )
36 - exit 1
37 - end
38 - else
39 - gem "rails"
40 - require 'initializer'
43 + class VendorBoot < Boot
44 + def load_initializer
45 + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
46 + Rails::Initializer.run(:install_gem_spec_stubs)
41 47 end
42 48 end
43 49
44 - Rails::Initializer.run(:set_load_path)
50 + class GemBoot < Boot
51 + def load_initializer
52 + self.class.load_rubygems
53 + load_rails_gem
54 + require 'initializer'
55 + end
56 +
57 + def load_rails_gem
58 + if version = self.class.gem_version
59 + gem 'rails', version
60 + else
61 + gem 'rails'
62 + end
63 + rescue Gem::LoadError => load_error
64 + $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
65 + exit 1
66 + end
67 +
68 + class << self
69 + def rubygems_version
70 + Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion
71 + end
72 +
73 + def gem_version
74 + if defined? RAILS_GEM_VERSION
75 + RAILS_GEM_VERSION
76 + elsif ENV.include?('RAILS_GEM_VERSION')
77 + ENV['RAILS_GEM_VERSION']
78 + else
79 + parse_gem_version(read_environment_rb)
80 + end
81 + end
82 +
83 + def load_rubygems
84 + require 'rubygems'
85 +
86 + unless rubygems_version >= '0.9.4'
87 + $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.)
88 + exit 1
89 + end
90 +
91 + rescue LoadError
92 + $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org)
93 + exit 1
94 + end
95 +
96 + def parse_gem_version(text)
97 + $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
98 + end
99 +
100 + private
101 + def read_environment_rb
102 + File.read("#{RAILS_ROOT}/config/environment.rb")
103 + end
104 + end
105 + end
45 106 end
107 +
108 + # All that for this:
109 + Rails.boot!
This diff has been collapsed as it changes many lines, (840 lines changed) Show them Hide them
@@ -1,6 +1,6
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-2008 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
6 6 // Rahul Bhargava
@@ -37,22 +37,23
37 37 if(typeof Effect == 'undefined')
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;
47 47 this.changed = false;
48 48 this.active = 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;
58 59 this.options.tokens = this.options.tokens || [];
@@ -74,6 +75,9
74 75
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;
79 83
@@ -81,15 +85,14
81 85
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
88 92 show: function() {
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,
95 98 '<iframe id="' + this.update.id + '_iefix" '+
@@ -139,17 +142,17
139 142 case Event.KEY_UP:
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;
155 158 this.hasFocus = true;
@@ -195,7 +198,6
195 198 this.index==i ?
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();
201 203 this.active = true;
@@ -238,21 +240,22
238 240 }
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
258 261 if (this.options.afterUpdateElement)
@@ -296,39 +299,48
296 299
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 {
303 306 this.active = false;
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;
313 + var bounds = this.getTokenBounds();
314 + return this.element.value.substring(bounds[0], bounds[1]).strip();
316 315 },
317 316
318 - findLastToken: function() {
319 - var lastTokenPos = -1;
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]);
332 + }
333 + });
320 334
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;
327 - }
328 - }
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 + };
329 342
330 - Ajax.Autocompleter = Class.create();
331 - Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
343 + Ajax.Autocompleter = Class.create(Autocompleter.Base, {
332 344 initialize: function(element, update, url, options) {
333 345 this.baseInitialize(element, update, options);
334 346 this.options.asynchronous = true;
@@ -338,7 +350,9
338 350 },
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
344 358 this.options.parameters = this.options.callback ?
@@ -346,14 +360,13
346 360
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 },
352 366
353 367 onComplete: function(request) {
354 368 this.updateChoices(request.responseText);
355 369 }
356 -
357 370 });
358 371
359 372 // The local array autocompleter. Used when you'd prefer to
@@ -391,8 +404,7
391 404 // In that case, the other options above will not apply unless
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);
398 410 this.options.array = array;
@@ -448,13 +460,12
448 460 ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
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,
460 471 // the DOM might be a bit confused when this gets called so do this
@@ -465,353 +476,472
465 476 }, 1);
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 - }
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 = '';
513 491 }
514 -
515 - if (this.options.externalControl) {
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);
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;
536 534 }
537 535 },
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);
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;
545 548 }
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;
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);
555 558 },
556 559 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 -
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;
562 570 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, "");
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);
592 580 },
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);
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();
594 + this.createForm();
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 },
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 - );
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);
649 619 },
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;
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);
666 644 }
667 - return false;
645 + if (e) Event.stop(e);
668 646 },
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;
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');
704 665 },
705 - onLoading: function() {
706 - this.saving = true;
666 + loadExternalText: function() {
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 },
711 - showSaving: function() {
712 - this.oldInnerHTML = this.element.innerHTML;
713 - 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);
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));
717 715 },
718 716 removeForm: function() {
719 - if(this.form) {
720 - if (this.form.parentNode) Element.remove(this.form);
721 - this.form = null;
717 + if (!this._form) return;
718 + this._form.remove();
719 + this._form = null;
720 + this._controls = { };
721 + },
722 + showSaving: function() {
723 + this._oldInnerHTML = this.element.innerHTML;
724 + this.element.innerHTML = this.options.savingText;
725 + this.element.addClassName(this.options.savingClassName);
726 + this.element.style.backgroundColor = this._originalBackground;
727 + this.element.show();
728 + },
729 + triggerCallback: function(cbName, arg) {
730 + if ('function' == typeof this.options[cbName]) {
731 + this.options[cbName](this, arg);
722 732 }
723 733 },
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)
734 + unregisterListeners: function() {
735 + $H(this._listeners).each(function(pair) {
736 + if (!this.options.externalControlOnly)
737 + this.element.stopObserving(pair.key, pair.value);
738 + if (this.options.externalControl)
739 + this.options.externalControl.stopObserving(pair.key, pair.value);
740 + }.bind(this));
741 + },
742 + wrapUp: function(transport) {
743 + this.leaveEditMode();
744 + // Can't use triggerCallback due to backward compatibility: requires
745 + // binding + direct element
746 + this._boundComplete(transport, this.element);
747 + }
748 + });
749 +
750 + Object.extend(Ajax.InPlaceEditor.prototype, {
751 + dispose: Ajax.InPlaceEditor.prototype.destroy
752 + });
753 +
754 + Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
755 + initialize: function($super, element, url, options) {
756 + this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
757 + $super(element, url, options);
731 758 },
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
759 +
760 + createEditField: function() {
761 + var list = document.createElement('select');
762 + list.name = this.options.paramName;
763 + list.size = 1;
764 + this._controls.editor = list;
765 + this._collection = this.options.collection || [];
766 + if (this.options.loadCollectionURL)
767 + this.loadCollection();
768 + else
769 + this.checkForExternalText();
770 + this._form.appendChild(this._controls.editor);
771 + },
772 +
773 + loadCollection: function() {
774 + this._form.addClassName(this.options.loadingClassName);
775 + this.showLoadingText(this.options.loadingCollectionText);
776 + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
777 + Object.extend(options, {
778 + parameters: 'editorId=' + encodeURIComponent(this.element.id),
779 + onComplete: Prototype.emptyFunction,
780 + onSuccess: function(transport) {
781 + var js = transport.responseText.strip();
782 + if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
783 + throw 'Server returned an invalid collection representation.';
784 + this._collection = eval(js);
785 + this.checkForExternalText();
786 + }.bind(this),
787 + onFailure: this.onFailure
742 788 });
789 + new Ajax.Request(this.options.loadCollectionURL, options);
743 790 },
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);
791 +
792 + showLoadingText: function(text) {
793 + this._controls.editor.disabled = true;
794 + var tempOption = this._controls.editor.firstChild;
795 + if (!tempOption) {
796 + tempOption = document.createElement('option');
797 + tempOption.value = '';
798 + this._controls.editor.appendChild(tempOption);
799 + tempOption.selected = true;
752 800 }
753 - this.editing = false;
754 - this.saving = false;
755 - this.oldInnerHTML = null;
756 - this.onLeaveEditMode();
801 + tempOption.update((text || '').stripScripts().stripTags());
802 + },
803 +
804 + checkForExternalText: function() {
805 + this._text = this.getText();
806 + if (this.options.loadTextURL)
807 + this.loadExternalText();
808 + else
809 + this.buildOptionList();
810 + },
811 +
812 + loadExternalText: function() {
813 + this.showLoadingText(this.options.loadingText);
814 + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
815 + Object.extend(options, {
816 + parameters: 'editorId=' + encodeURIComponent(this.element.id),
817 + onComplete: Prototype.emptyFunction,
818 + onSuccess: function(transport) {
819 + this._text = transport.responseText.strip();
820 + this.buildOptionList();
821 + }.bind(this),
822 + onFailure: this.onFailure
823 + });
824 + new Ajax.Request(this.options.loadTextURL, options);
757 825 },
758 - onComplete: function(transport) {
759 - this.leaveEditMode();
760 - this.options.onComplete.bind(this)(transport, this.element);
761 - },
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 - }
826 +
827 + buildOptionList: function() {
828 + this._form.removeClassName(this.options.loadingClassName);
829 + this._collection = this._collection.map(function(entry) {
830 + return 2 === entry.length ? entry : [entry, entry].flatten();
831 + });
832 + var marker = ('value' in this.options) ? this.options.value : this._text;
833 + var textFound = this._collection.any(function(entry) {
834 + return entry[0] == marker;
835 + }.bind(this));
836 + this._controls.editor.update('');
837 + var option;
838 + this._collection.each(function(entry, index) {
839 + option = document.createElement('option');
840 + option.value = entry[0];
841 + option.selected = textFound ? entry[0] == marker : 0 == index;
842 + option.appendChild(document.createTextNode(entry[1]));
843 + this._controls.editor.appendChild(option);
844 + }.bind(this));
845 + this._controls.editor.disabled = false;
846 + Field.scrollFreeActivate(this._controls.editor);
777 847 }
848 + });
849 +
850 + //**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
851 + //**** This only exists for a while, in order to let ****
852 + //**** users adapt to the new API. Read up on the new ****
853 + //**** API and convert your code to it ASAP! ****
854 +
855 + Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
856 + if (!options) return;
857 + function fallback(name, expr) {
858 + if (name in options || expr === undefined) return;
859 + options[name] = expr;
860 + };
861 + fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
862 + options.cancelLink == options.cancelButton == false ? false : undefined)));
863 + fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
864 + options.okLink == options.okButton == false ? false : undefined)));
865 + fallback('highlightColor', options.highlightcolor);
866 + fallback('highlightEndColor', options.highlightendcolor);
778 867 };
779 868
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;
869 + Object.extend(Ajax.InPlaceEditor, {
870 + DefaultOptions: {
871 + ajaxOptions: { },
872 + autoRows: 3, // Use when multi-line w/ rows == 1
873 + cancelControl: 'link', // 'link'|'button'|false
874 + cancelText: 'cancel',
875 + clickToEditText: 'Click to edit',
876 + externalControl: null, // id|elt
877 + externalControlOnly: false,
878 + fieldPostCreation: 'activate', // 'activate'|'focus'|false
879 + formClassName: 'inplaceeditor-form',
880 + formId: null, // id|elt
881 + highlightColor: '#ffff99',
882 + highlightEndColor: '#ffffff',
883 + hoverClassName: '',
884 + htmlResponse: true,
885 + loadingClassName: 'inplaceeditor-loading',
886 + loadingText: 'Loading...',
887 + okControl: 'button', // 'link'|'button'|false
888 + okText: 'ok',
889 + paramName: 'value',
890 + rows: 1, // If 1 and multi-line, uses autoRows
891 + savingClassName: 'inplaceeditor-saving',
892 + savingText: 'Saving...',
893 + size: 0,
894 + stripLoadedTextTags: false,
895 + submitOnBlur: false,
896 + textAfterControls: '',
897 + textBeforeControls: '',
898 + textBetweenControls: ''
899 + },
900 + DefaultCallbacks: {
901 + callback: function(form) {
902 + return Form.serialize(form);
903 + },
904 + onComplete: function(transport, element) {
905 + // For backward compatibility, this one is bound to the IPE, and passes
906 + // the element directly. It was too often customized, so we don't break it.
907 + new Effect.Highlight(element, {
908 + startcolor: this.options.highlightColor, keepBackgroundImage: true });
909 + },
910 + onEnterEditMode: null,
911 + onEnterHover: function(ipe) {
912 + ipe.element.style.backgroundColor = ipe.options.highlightColor;
913 + if (ipe._effect)
914 + ipe._effect.cancel();
915 + },
916 + onFailure: function(transport, ipe) {
917 + alert('Error communication with the server: ' + transport.responseText.stripTags());
918 + },
919 + onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
920 + onLeaveEditMode: null,
921 + onLeaveHover: function(ipe) {
922 + ipe._effect = new Effect.Highlight(ipe.element, {
923 + startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
924 + restorecolor: ipe._originalBackground, keepBackgroundImage: true
925 + });
798 926 }
799 -
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);
805 - }
927 + },
928 + Listeners: {
929 + click: 'enterEditMode',
930 + keydown: 'checkForEscapeOrReturn',
931 + mouseover: 'enterHover',
932 + mouseout: 'leaveHover'
806 933 }
807 934 });
808 935
936 + Ajax.InPlaceCollectionEditor.DefaultOptions = {
937 + loadingCollectionText: 'Loading options...'
938 + };
939 +
809 940 // Delayed observer, like Form.Element.Observer,
810 941 // but waits for delay after last key input
811 942 // Ideal for live-search fields
812 943
813 - Form.Element.DelayedObserver = Class.create();
814 - Form.Element.DelayedObserver.prototype = {
944 + Form.Element.DelayedObserver = Class.create({
815 945 initialize: function(element, delay, callback) {
816 946 this.delay = delay || 0.5;
817 947 this.element = $(element);
@@ -830,4 +960,4
830 960 this.timer = null;
831 961 this.callback(this.element, $F(this.element));
832 962 }
833 - };
963 + });
@@ -1,10 +1,10
1 - // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 - // (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
1 + // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 + // (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
3 3 //
4 4 // script.aculo.us is freely distributable under the terms of an MIT-style license.
5 5 // For details, see the script.aculo.us web site: http://script.aculo.us/
6 6
7 - if(typeof Effect == 'undefined')
7 + if(Object.isUndefined(Effect))
8 8 throw("dragdrop.js requires including script.aculo.us' effects.js library");
9 9
10 10 var Droppables = {
@@ -20,14 +20,13
20 20 greedy: true,
21 21 hoverclass: null,
22 22 tree: false
23 - }, arguments[1] || {});
23 + }, arguments[1] || { });
24 24
25 25 // cache containers
26 26 if(options.containment) {
27 27 options._containers = [];
28 28 var containment = options.containment;
29 - if((typeof containment == 'object') &&
30 - (containment.constructor == Array)) {
29 + if(Object.isArray(containment)) {
31 30 containment.each( function(c) { options._containers.push($(c)) });
32 31 } else {
33 32 options._containers.push($(containment));
@@ -87,21 +86,23
87 86
88 87 show: function(point, element) {
89 88 if(!this.drops.length) return;
90 - var affected = [];
89 + var drop, affected = [];
91 90
92 - if(this.last_active) this.deactivate(this.last_active);
93 91 this.drops.each( function(drop) {
94 92 if(Droppables.isAffected(point, element, drop))
95 93 affected.push(drop);
96 94 });
97 95
98 - if(affected.length>0) {
96 + if(affected.length>0)
99 97 drop = Droppables.findDeepestChild(affected);
98 +
99 + if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
100 + if (drop) {
100 101 Position.within(drop.element, point[0], point[1]);
101 102 if(drop.onHover)
102 103 drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
103 104
104 - Droppables.activate(drop);
105 + if (drop != this.last_active) Droppables.activate(drop);
105 106 }
106 107 },
107 108
@@ -110,8 +111,10
110 111 Position.prepare();
111 112
112 113 if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
113 - if (this.last_active.onDrop)
114 - this.last_active.onDrop(element, this.last_active.element, event);
114 + if (this.last_active.onDrop) {
115 + this.last_active.onDrop(element, this.last_active.element, event);
116 + return true;
117 + }
115 118 },
116 119
117 120 reset: function() {
@@ -219,10 +222,7
219 222
220 223 /*--------------------------------------------------------------------------*/
221 224
222 - var Draggable = Class.create();
223 - Draggable._dragging = {};
224 -
225 - Draggable.prototype = {
225 + var Draggable = Class.create({
226 226 initialize: function(element) {
227 227 var defaults = {
228 228 handle: false,
@@ -233,7 +233,7
233 233 });
234 234 },
235 235 endeffect: function(element) {
236 - var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0;
236 + var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
237 237 new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
238 238 queue: {scope:'_draggable', position:'end'},
239 239 afterFinish: function(){
@@ -243,6 +243,7
243 243 },
244 244 zindex: 1000,
245 245 revert: false,
246 + quiet: false,
246 247 scroll: false,
247 248 scrollSensitivity: 20,
248 249 scrollSpeed: 15,
@@ -250,7 +251,7
250 251 delay: 0
251 252 };
252 253
253 - if(!arguments[1] || typeof arguments[1].endeffect == 'undefined')
254 + if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
254 255 Object.extend(defaults, {
255 256 starteffect: function(element) {
256 257 element._opacity = Element.getOpacity(element);
@@ -259,11 +260,11
259 260 }
260 261 });
261 262
262 - var options = Object.extend(defaults, arguments[1] || {});
263 + var options = Object.extend(defaults, arguments[1] || { });
263 264
264 265 this.element = $(element);
265 266
266 - if(options.handle && (typeof options.handle == 'string'))
267 + if(options.handle && Object.isString(options.handle))
267 268 this.handle = this.element.down('.'+options.handle, 0);
268 269
269 270 if(!this.handle) this.handle = $(options.handle);
@@ -276,7 +277,6
276 277
277 278 Element.makePositioned(this.element); // fix IE
278 279
279 - this.delta = this.currentDelta();
280 280 this.options = options;
281 281 this.dragging = false;
282 282
@@ -298,17 +298,17
298 298 },
299 299
300 300 initDrag: function(event) {
301 - if(typeof Draggable._dragging[this.element] != 'undefined' &&
301 + if(!Object.isUndefined(Draggable._dragging[this.element]) &&
302 302 Draggable._dragging[this.element]) return;
303 303 if(Event.isLeftClick(event)) {
304 304 // abort on form elements, fixes a Firefox issue
305 305 var src = Event.element(event);
306 - if(src.tagName && (
307 - src.tagName=='INPUT' ||
308 - src.tagName=='SELECT' ||
309 - src.tagName=='OPTION' ||
310 - src.tagName=='BUTTON' ||
311 - src.tagName=='TEXTAREA')) return;
306 + if((tag_name = src.tagName.toUpperCase()) && (
307 + tag_name=='INPUT' ||
308 + tag_name=='SELECT' ||
309 + tag_name=='OPTION' ||
310 + tag_name=='BUTTON' ||
311 + tag_name=='TEXTAREA')) return;
312 312
313 313 var pointer = [Event.pointerX(event), Event.pointerY(event)];
314 314 var pos = Position.cumulativeOffset(this.element);
@@ -321,6 +321,8
321 321
322 322 startDrag: function(event) {
323 323 this.dragging = true;
324 + if(!this.delta)
325 + this.delta = this.currentDelta();
324 326
325 327 if(this.options.zindex) {
326 328 this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
@@ -329,7 +331,9
329 331
330 332 if(this.options.ghosting) {
331 333 this._clone = this.element.cloneNode(true);
332 - Position.absolutize(this.element);
334 + this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
335 + if (!this.element._originallyAbsolute)
336 + Position.absolutize(this.element);
333 337 this.element.parentNode.insertBefore(this._clone, this.element);
334 338 }
335 339
@@ -351,8 +355,12
351 355
352 356 updateDrag: function(event, pointer) {
353 357 if(!this.dragging) this.startDrag(event);
354 - Position.prepare();
355 - Droppables.show(pointer, this.element);
358 +
359 + if(!this.options.quiet){
360 + Position.prepare();
361 + Droppables.show(pointer, this.element);
362 + }
363 +
356 364 Draggables.notify('onDrag', this, event);
357 365
358 366 this.draw(pointer);
@@ -380,30 +388,44
380 388 }
381 389
382 390 // fix AppleWebKit rendering
383 - if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
391 + if(Prototype.Browser.WebKit) window.scrollBy(0,0);
384 392
385 393 Event.stop(event);
386 394 },
387 395
388 396 finishDrag: function(event, success) {
389 397 this.dragging = false;
398 +
399 + if(this.options.quiet){
400 + Position.prepare();
401 + var pointer = [Event.pointerX(event), Event.pointerY(event)];
402 + Droppables.show(pointer, this.element);
403 + }
390 404
391 405 if(this.options.ghosting) {
392 - Position.relativize(this.element);
406 + if (!this.element._originallyAbsolute)
407 + Position.relativize(this.element);
408 + delete this.element._originallyAbsolute;
393 409 Element.remove(this._clone);
394 410 this._clone = null;
395 411 }
396 412
397 - if(success) Droppables.fire(event, this.element);
413 + var dropped = false;
414 + if(success) {
415 + dropped = Droppables.fire(event, this.element);
416 + if (!dropped) dropped = false;
417 + }
418 + if(dropped && this.options.onDropped) this.options.onDropped(this.element);
398 419 Draggables.notify('onEnd', this, event);
399 420
400 421 var revert = this.options.revert;
401 - if(revert && typeof revert == 'function') revert = revert(this.element);
422 + if(revert && Object.isFunction(revert)) revert = revert(this.element);
402 423
403 424 var d = this.currentDelta();
404 425 if(revert && this.options.reverteffect) {
405 - this.options.reverteffect(this.element,
406 - d[1]-this.delta[1], d[0]-this.delta[0]);
426 + if (dropped == 0 || revert != 'failure')
427 + this.options.reverteffect(this.element,
428 + d[1]-this.delta[1], d[0]-this.delta[0]);
407 429 } else {
408 430 this.delta = d;
409 431 }
@@ -451,15 +473,15
451 473 }.bind(this));
452 474
453 475 if(this.options.snap) {
454 - if(typeof this.options.snap == 'function') {
476 + if(Object.isFunction(this.options.snap)) {
455 477 p = this.options.snap(p[0],p[1],this);
456 478 } else {
457 - if(this.options.snap instanceof Array) {
479 + if(Object.isArray(this.options.snap)) {
458 480 p = p.map( function(v, i) {
459 - return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
481 + return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
460 482 } else {
461 483 p = p.map( function(v) {
462 - return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
484 + return (v/this.options.snap).round()*this.options.snap }.bind(this))
463 485 }
464 486 }}
465 487
@@ -543,12 +565,13
543 565 }
544 566 return { top: T, left: L, width: W, height: H };
545 567 }
546 - }
568 + });
569 +
570 + Draggable._dragging = { };
547 571
548 572 /*--------------------------------------------------------------------------*/
549 573
550 - var SortableObserver = Class.create();
551 - SortableObserver.prototype = {
574 + var SortableObserver = Class.create({
552 575 initialize: function(element, observer) {
553 576 this.element = $(element);
554 577 this.observer = observer;
@@ -564,15 +587,15
564 587 if(this.lastValue != Sortable.serialize(this.element))
565 588 this.observer(this.element)
566 589 }
567 - }
590 + });
568 591
569 592 var Sortable = {
570 593 SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
571 594
572 - sortables: {},
595 + sortables: { },
573 596
574 597 _findRootElement: function(element) {
575 - while (element.tagName != "BODY") {
598 + while (element.tagName.toUpperCase() != "BODY") {
576 599 if(element.id && Sortable.sortables[element.id]) return element;
577 600 element = element.parentNode;
578 601 }
@@ -612,13 +635,20
612 635 delay: 0,
613 636 hoverclass: null,
614 637 ghosting: false,
638 + quiet: false,
615 639 scroll: false,
616 640 scrollSensitivity: 20,
617 641 scrollSpeed: 15,
618 642 format: this.SERIALIZE_RULE,
643 +
644 + // these take arrays of elements or ids and can be
645 + // used for better initialization performance
646 + elements: false,
647 + handles: false,
648 +
619 649 onChange: Prototype.emptyFunction,
620 650 onUpdate: Prototype.emptyFunction
621 - }, arguments[1] || {});
651 + }, arguments[1] || { });
622 652
623 653 // clear any old sortable with same element
624 654 this.destroy(element);
@@ -626,6 +656,7
626 656 // build options for the draggables
627 657 var options_for_draggable = {
628 658 revert: true,
659 + quiet: options.quiet,
629 660 scroll: options.scroll,
630 661 scrollSpeed: options.scrollSpeed,
631 662 scrollSensitivity: options.scrollSensitivity,
@@ -679,10 +710,9
679 710 options.droppables.push(element);
680 711 }
681 712
682 - (this.findElements(element, options) || []).each( function(e) {
683 - // handles are per-draggable
684 - var handle = options.handle ?
685 - $(e).down('.'+options.handle,0) : e;
713 + (options.elements || this.findElements(element, options) || []).each( function(e,i) {
714 + var handle = options.handles ? $(options.handles[i]) :
715 + (options.handle ? $(e).select('.' + options.handle)[0] : e);
686 716 options.draggables.push(
687 717 new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
688 718 Droppables.add(e, options_for_droppable);
@@ -842,7 +872,7
842 872 only: sortableOptions.only,
843 873 name: element.id,
844 874 format: sortableOptions.format
845 - }, arguments[1] || {});
875 + }, arguments[1] || { });
846 876
847 877 var root = {
848 878 id: null,
@@ -866,7 +896,7
866 896
867 897 sequence: function(element) {
868 898 element = $(element);
869 - var options = Object.extend(this.options(element), arguments[1] || {});
899 + var options = Object.extend(this.options(element), arguments[1] || { });
870 900
871 901 return $(this.findElements(element, options) || []).map( function(item) {
872 902 return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
@@ -875,9 +905,9
875 905
876 906 setSequence: function(element, new_sequence) {
877 907 element = $(element);
878 - var options = Object.extend(this.options(element), arguments[2] || {});
908 + var options = Object.extend(this.options(element), arguments[2] || { });
879 909
880 - var nodeMap = {};
910 + var nodeMap = { };
881 911 this.findElements(element, options).each( function(n) {
882 912 if (n.id.match(options.format))
883 913 nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
@@ -895,7 +925,7
895 925
896 926 serialize: function(element) {
897 927 element = $(element);
898 - var options = Object.extend(Sortable.options(element), arguments[1] || {});
928 + var options = Object.extend(Sortable.options(element), arguments[1] || { });
899 929 var name = encodeURIComponent(
900 930 (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
901 931
@@ -919,7 +949,7
919 949 return Element.isParent(child.parentNode, element);
920 950 }
921 951
922 - Element.findChildren = function(element, only, recursive, tagName) {
952 + Element.findChildren = function(element, only, recursive, tagName) {
923 953 if(!element.hasChildNodes()) return null;
924 954 tagName = tagName.toUpperCase();
925 955 if(only) only = [only].flatten();
This diff has been collapsed as it changes many lines, (760 lines changed) Show them Hide them
@@ -1,4 +1,4
1 - // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
1 + // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 2 // Contributors:
3 3 // Justin Palmer (http://encytemedia.com/)
4 4 // Mark Pilgrim (http://diveintomark.org/)
@@ -11,17 +11,17
11 11 // returns self (or first argument) if not convertable
12 12 String.prototype.parseColor = function() {
13 13 var color = '#';
14 - if(this.slice(0,4) == 'rgb(') {
14 + if (this.slice(0,4) == 'rgb(') {
15 15 var cols = this.slice(4,this.length-1).split(',');
16 16 var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
17 17 } else {
18 - if(this.slice(0,1) == '#') {
19 - if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
20 - if(this.length==7) color = this.toLowerCase();
18 + if (this.slice(0,1) == '#') {
19 + if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
20 + if (this.length==7) color = this.toLowerCase();
21 21 }
22 22 }
23 - return(color.length==7 ? color : (arguments[0] || this));
24 - }
23 + return (color.length==7 ? color : (arguments[0] || this));
24 + };
25 25
26 26 /*--------------------------------------------------------------------------*/
27 27
@@ -30,7 +30,7
30 30 return (node.nodeType==3 ? node.nodeValue :
31 31 (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
32 32 }).flatten().join('');
33 - }
33 + };
34 34
35 35 Element.collectTextNodesIgnoreClass = function(element, className) {
36 36 return $A($(element).childNodes).collect( function(node) {
@@ -38,47 +38,18
38 38 ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
39 39 Element.collectTextNodesIgnoreClass(node, className) : ''));
40 40 }).flatten().join('');
41 - }
41 + };
42 42
43 43 Element.setContentZoom = function(element, percent) {
44 44 element = $(element);
45 45 element.setStyle({fontSize: (percent/100) + 'em'});
46 - if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
46 + if (Prototype.Browser.WebKit) window.scrollBy(0,0);
47 47 return element;
48 - }
49 -
50 - Element.getOpacity = function(element){
51 - element = $(element);
52 - var opacity;
53 - if (opacity = element.getStyle('opacity'))
54 - return parseFloat(opacity);
55 - if (opacity = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
56 - if(opacity[1]) return parseFloat(opacity[1]) / 100;
57 - return 1.0;
58 - }
48 + };
59 49
60 - Element.setOpacity = function(element, value){
61 - element= $(element);
62 - if (value == 1){
63 - element.setStyle({ opacity:
64 - (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ?
65 - 0.999999 : 1.0 });
66 - if(/MSIE/.test(navigator.userAgent) && !window.opera)
67 - element.setStyle({filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});
68 - } else {
69 - if(value < 0.00001) value = 0;
70 - element.setStyle({opacity: value});
71 - if(/MSIE/.test(navigator.userAgent) && !window.opera)
72 - element.setStyle(
73 - { filter: element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') +
74 - 'alpha(opacity='+value*100+')' });
75 - }
76 - return element;
77 - }
78 -
79 - Element.getInlineOpacity = function(element){
50 + Element.getInlineOpacity = function(element){
80 51 return $(element).style.opacity || '';
81 - }
52 + };
82 53
83 54 Element.forceRerendering = function(element) {
84 55 try {
@@ -91,31 +62,63
91 62
92 63 /*--------------------------------------------------------------------------*/
93 64
94 - Array.prototype.call = function() {
95 - var args = arguments;
96 - this.each(function(f){ f.apply(this, args) });
97 - }
98 -
99 - /*--------------------------------------------------------------------------*/
100 -
101 65 var Effect = {
102 66 _elementDoesNotExistError: {
103 67 name: 'ElementDoesNotExistError',
104 68 message: 'The specified DOM element does not exist, but is required for this effect to operate'
105 69 },
70 + Transitions: {
71 + linear: Prototype.K,
72 + sinoidal: function(pos) {
73 + return (-Math.cos(pos*Math.PI)/2) + 0.5;
74 + },
75 + reverse: function(pos) {
76 + return 1-pos;
77 + },
78 + flicker: function(pos) {
79 + var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
80 + return pos > 1 ? 1 : pos;
81 + },
82 + wobble: function(pos) {
83 + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
84 + },
85 + pulse: function(pos, pulses) {
86 + pulses = pulses || 5;
87 + return (
88 + ((pos % (1/pulses)) * pulses).round() == 0 ?
89 + ((pos * pulses * 2) - (pos * pulses * 2).floor()) :
90 + 1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
91 + );
92 + },
93 + spring: function(pos) {
94 + return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
95 + },
96 + none: function(pos) {
97 + return 0;
98 + },
99 + full: function(pos) {
100 + return 1;
101 + }
102 + },
103 + DefaultOptions: {
104 + duration: 1.0, // seconds
105 + fps: 100, // 100= assume 66fps max.
106 + sync: false, // true for combining
107 + from: 0.0,
108 + to: 1.0,
109 + delay: 0.0,
110 + queue: 'parallel'
111 + },
106 112 tagifyText: function(element) {
107 - if(typeof Builder == 'undefined')
108 - throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
109 -
110 113 var tagifyStyle = 'position:relative';
111 - if(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1';
114 + if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
112 115
113 116 element = $(element);
114 117 $A(element.childNodes).each( function(child) {
115 - if(child.nodeType==3) {
118 + if (child.nodeType==3) {
116 119 child.nodeValue.toArray().each( function(character) {
117 120 element.insertBefore(
118 - Builder.node('span',{style: tagifyStyle},
121 + new Element('span', {style: tagifyStyle}).update(
119 122 character == ' ' ? String.fromCharCode(160) : character),
120 123 child);
121 124 });
@@ -125,8 +128,8
125 128 },
126 129 multiple: function(element, effect) {
127 130 var elements;
128 - if(((typeof element == 'object') ||
129 - (typeof element == 'function')) &&
131 + if (((typeof element == 'object') ||
132 + Object.isFunction(element)) &&
130 133 (element.length))
131 134 elements = element;
132 135 else
@@ -135,7 +138,7
135 138 var options = Object.extend({
136 139 speed: 0.1,
137 140 delay: 0.0
138 - }, arguments[2] || {});
141 + }, arguments[2] || { });
139 142 var masterDelay = options.delay;
140 143
141 144 $A(elements).each( function(element, index) {
@@ -152,53 +155,20
152 155 effect = (effect || 'appear').toLowerCase();
153 156 var options = Object.extend({
154 157 queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
155 - }, arguments[2] || {});
158 + }, arguments[2] || { });
156 159 Effect[element.visible() ?
157 160 Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
158 161 }
159 162 };
160 163
161 - var Effect2 = Effect; // deprecated
162 -
163 - /* ------------- transitions ------------- */
164 -
165 - Effect.Transitions = {
166 - linear: Prototype.K,
167 - sinoidal: function(pos) {
168 - return (-Math.cos(pos*Math.PI)/2) + 0.5;
169 - },
170 - reverse: function(pos) {
171 - return 1-pos;
172 - },
173 - flicker: function(pos) {
174 - return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
175 - },
176 - wobble: function(pos) {
177 - return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
178 - },
179 - pulse: function(pos, pulses) {
180 - pulses = pulses || 5;
181 - return (
182 - Math.round((pos % (1/pulses)) * pulses) == 0 ?
183 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) :
184 - 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2))
185 - );
186 - },
187 - none: function(pos) {
188 - return 0;
189 - },
190 - full: function(pos) {
191 - return 1;
192 - }
193 - };
164 + Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
194 165
195 166 /* ------------- core effects ------------- */
196 167
197 - Effect.ScopedQueue = Class.create();
198 - Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
168 + Effect.ScopedQueue = Class.create(Enumerable, {
199 169 initialize: function() {
200 170 this.effects = [];
201 - this.interval = null;
171 + this.interval = null;
202 172 },
203 173 _each: function(iterator) {
204 174 this.effects._each(iterator);
@@ -206,7 +176,7
206 176 add: function(effect) {
207 177 var timestamp = new Date().getTime();
208 178
209 - var position = (typeof effect.options.queue == 'string') ?
179 + var position = Object.isString(effect.options.queue) ?
210 180 effect.options.queue : effect.options.queue.position;
211 181
212 182 switch(position) {
@@ -229,115 +199,111
229 199 effect.startOn += timestamp;
230 200 effect.finishOn += timestamp;
231 201
232 - if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
202 + if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
233 203 this.effects.push(effect);
234 204
235 - if(!this.interval)
236 - this.interval = setInterval(this.loop.bind(this), 40);
205 + if (!this.interval)
206 + this.interval = setInterval(this.loop.bind(this), 15);
237 207 },
238 208 remove: function(effect) {
239 209 this.effects = this.effects.reject(function(e) { return e==effect });
240 - if(this.effects.length == 0) {
210 + if (this.effects.length == 0) {
241 211 clearInterval(this.interval);
242 212 this.interval = null;
243 213 }
244 214 },
245 215 loop: function() {
246 216 var timePos = new Date().getTime();
247 - this.effects.invoke('loop', timePos);
217 + for(var i=0, len=this.effects.length;i<len;i++)
218 + this.effects[i] && this.effects[i].loop(timePos);
248 219 }
249 220 });
250 221
251 222 Effect.Queues = {
252 223 instances: $H(),
253 224 get: function(queueName) {
254 - if(typeof queueName != 'string') return queueName;
225 + if (!Object.isString(queueName)) return queueName;
255 226
256 - if(!this.instances[queueName])
257 - this.instances[queueName] = new Effect.ScopedQueue();
258 -
259 - return this.instances[queueName];
227 + return this.instances.get(queueName) ||
228 + this.instances.set(queueName, new Effect.ScopedQueue());
260 229 }
261 - }
230 + };
262 231 Effect.Queue = Effect.Queues.get('global');
263 232
264 - Effect.DefaultOptions = {
265 - transition: Effect.Transitions.sinoidal,
266 - duration: 1.0, // seconds
267 - fps: 25.0, // max. 25fps due to Effect.Queue implementation
268 - sync: false, // true for combining
269 - from: 0.0,
270 - to: 1.0,
271 - delay: 0.0,
272 - queue: 'parallel'
273 - }
274 -
275 - Effect.Base = function() {};
276 - Effect.Base.prototype = {
233 + Effect.Base = Class.create({
277 234 position: null,
278 235 start: function(options) {
279 - this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
236 + function codeForEvent(options,eventName){
237 + return (
238 + (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
239 + (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
240 + );
241 + }
242 + if (options && options.transition === false) options.transition = Effect.Transitions.linear;
243 + this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
280 244 this.currentFrame = 0;
281 245 this.state = 'idle';
282 246 this.startOn = this.options.delay*1000;
283 - this.finishOn = this.startOn + (this.options.duration*1000);
247 + this.finishOn = this.startOn+(this.options.duration*1000);
248 + this.fromToDelta = this.options.to-this.options.from;
249 + this.totalTime = this.finishOn-this.startOn;
250 + this.totalFrames = this.options.fps*this.options.duration;
251 +
252 + eval('this.render = function(pos){ '+
253 + 'if (this.state=="idle"){this.state="running";'+
254 + codeForEvent(this.options,'beforeSetup')+
255 + (this.setup ? 'this.setup();':'')+
256 + codeForEvent(this.options,'afterSetup')+
257 + '};if (this.state=="running"){'+
258 + 'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
259 + 'this.position=pos;'+
260 + codeForEvent(this.options,'beforeUpdate')+
261 + (this.update ? 'this.update(pos);':'')+
262 + codeForEvent(this.options,'afterUpdate')+
263 + '}}');
264 +
284 265 this.event('beforeStart');
285 - if(!this.options.sync)
286 - Effect.Queues.get(typeof this.options.queue == 'string' ?
266 + if (!this.options.sync)
267 + Effect.Queues.get(Object.isString(this.options.queue) ?
287 268 'global' : this.options.queue.scope).add(this);
288 269 },
289 270 loop: function(timePos) {
290 - if(timePos >= this.startOn) {
291 - if(timePos >= this.finishOn) {
271 + if (timePos >= this.startOn) {
272 + if (timePos >= this.finishOn) {
292 273 this.render(1.0);
293 274 this.cancel();
294 275 this.event('beforeFinish');
295 - if(this.finish) this.finish();
276 + if (this.finish) this.finish();
296 277 this.event('afterFinish');
297 278 return;
298 279 }
299 - var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
300 - var frame = Math.round(pos * this.options.fps * this.options.duration);
301 - if(frame > this.currentFrame) {
280 + var pos = (timePos - this.startOn) / this.totalTime,
281 + frame = (pos * this.totalFrames).round();
282 + if (frame > this.currentFrame) {
302 283 this.render(pos);
303 284 this.currentFrame = frame;
304 285 }
305 286 }
306 287 },
307 - render: function(pos) {
308 - if(this.state == 'idle') {
309 - this.state = 'running';
310 - this.event('beforeSetup');
311 - if(this.setup) this.setup();
312 - this.event('afterSetup');
313 - }
314 - if(this.state == 'running') {
315 - if(this.options.transition) pos = this.options.transition(pos);
316 - pos *= (this.options.to-this.options.from);
317 - pos += this.options.from;
318 - this.position = pos;
319 - this.event('beforeUpdate');
320 - if(this.update) this.update(pos);
321 - this.event('afterUpdate');
322 - }
323 - },
324 288 cancel: function() {
325 - if(!this.options.sync)
326 - Effect.Queues.get(typeof this.options.queue == 'string' ?
289 + if (!this.options.sync)
290 + Effect.Queues.get(Object.isString(this.options.queue) ?
327 291 'global' : this.options.queue.scope).remove(this);
328 292 this.state = 'finished';
329 293 },
330 294 event: function(eventName) {
331 - if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
332 - if(this.options[eventName]) this.options[eventName](this);
295 + if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
296 + if (this.options[eventName]) this.options[eventName](this);
333 297 },
334 298 inspect: function() {
335 - return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
299 + var data = $H();
300 + for(property in this)
301 + if (!Object.isFunction(this[property])) data.set(property, this[property]);
302 + return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
336 303 }
337 - }
304 + });
338 305
339 - Effect.Parallel = Class.create();
340 - Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
306 + Effect.Parallel = Class.create(Effect.Base, {
341 307 initialize: function(effects) {
342 308 this.effects = effects || [];
343 309 this.start(arguments[1]);
@@ -350,35 +316,45
350 316 effect.render(1.0);
351 317 effect.cancel();
352 318 effect.event('beforeFinish');
353 - if(effect.finish) effect.finish(position);
319 + if (effect.finish) effect.finish(position);
354 320 effect.event('afterFinish');
355 321 });
356 322 }
357 323 });
358 324
359 - Effect.Event = Class.create();
360 - Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), {
325 + Effect.Tween = Class.create(Effect.Base, {
326 + initialize: function(object, from, to) {
327 + object = Object.isString(object) ? $(object) : object;
328 + var args = $A(arguments), method = args.last(),
329 + options = args.length == 5 ? args[3] : null;
330 + this.method = Object.isFunction(method) ? method.bind(object) :
331 + Object.isFunction(object[method]) ? object[method].bind(object) :
332 + function(value) { object[method] = value };
333 + this.start(Object.extend({ from: from, to: to }, options || { }));
334 + },
335 + update: function(position) {
336 + this.method(position);
337 + }
338 + });
339 +
340 + Effect.Event = Class.create(Effect.Base, {
361 341 initialize: function() {
362 - var options = Object.extend({
363 - duration: 0
364 - }, arguments[0] || {});
365 - this.start(options);
342 + this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
366 343 },
367 344 update: Prototype.emptyFunction
368 345 });
369 346
370 - Effect.Opacity = Class.create();
371 - Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
347 + Effect.Opacity = Class.create(Effect.Base, {
372 348 initialize: function(element) {
373 349 this.element = $(element);
374 - if(!this.element) throw(Effect._elementDoesNotExistError);
350 + if (!this.element) throw(Effect._elementDoesNotExistError);
375 351 // make this work on IE on elements without 'layout'
376 - if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout))
352 + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
377 353 this.element.setStyle({zoom: 1});
378 354 var options = Object.extend({
379 355 from: this.element.getOpacity() || 0.0,
380 356 to: 1.0
381 - }, arguments[1] || {});
357 + }, arguments[1] || { });
382 358 this.start(options);
383 359 },
384 360 update: function(position) {
@@ -386,36 +362,30
386 362 }
387 363 });
388 364
389 - Effect.Move = Class.create();
390 - Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
365 + Effect.Move = Class.create(Effect.Base, {
391 366 initialize: function(element) {
392 367 this.element = $(element);
393 - if(!this.element) throw(Effect._elementDoesNotExistError);
368 + if (!this.element) throw(Effect._elementDoesNotExistError);
394 369 var options = Object.extend({
395 370 x: 0,
396 371 y: 0,
397 372 mode: 'relative'
398 - }, arguments[1] || {});
373 + }, arguments[1] || { });
399 374 this.start(options);
400 375 },
401 376 setup: function() {
402 - // Bug in Opera: Opera returns the "real" position of a static element or
403 - // relative element that does not have top/left explicitly set.
404 - // ==> Always set top and left for position relative elements in your stylesheets
405 - // (to 0 if you do not need them)
406 377 this.element.makePositioned();
407 378 this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
408 379 this.originalTop = parseFloat(this.element.getStyle('top') || '0');
409 - if(this.options.mode == 'absolute') {
410 - // absolute movement, so we need to calc deltaX and deltaY
380 + if (this.options.mode == 'absolute') {
411 381 this.options.x = this.options.x - this.originalLeft;
412 382 this.options.y = this.options.y - this.originalTop;
413 383 }
414 384 },
415 385 update: function(position) {
416 386 this.element.setStyle({
417 - left: Math.round(this.options.x * position + this.originalLeft) + 'px',
418 - top: Math.round(this.options.y * position + this.originalTop) + 'px'
387 + left: (this.options.x * position + this.originalLeft).round() + 'px',
388 + top: (this.options.y * position + this.originalTop).round() + 'px'
419 389 });
420 390 }
421 391 });
@@ -423,30 +393,29
423 393 // for backwards compatibility
424 394 Effect.MoveBy = function(element, toTop, toLeft) {
425 395 return new Effect.Move(element,
426 - Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
396 + Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
427 397 };
428 398
429 - Effect.Scale = Class.create();
430 - Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
399 + Effect.Scale = Class.create(Effect.Base, {
431 400 initialize: function(element, percent) {
432 401 this.element = $(element);
433 - if(!this.element) throw(Effect._elementDoesNotExistError);
402 + if (!this.element) throw(Effect._elementDoesNotExistError);
434 403 var options = Object.extend({
435 404 scaleX: true,
436 405 scaleY: true,
437 406 scaleContent: true,
438 407 scaleFromCenter: false,
439 - scaleMode: 'box', // 'box' or 'contents' or {} with provided values
408 + scaleMode: 'box', // 'box' or 'contents' or { } with provided values
440 409 scaleFrom: 100.0,
441 410 scaleTo: percent
442 - }, arguments[2] || {});
411 + }, arguments[2] || { });
443 412 this.start(options);
444 413 },
445 414 setup: function() {
446 415 this.restoreAfterFinish = this.options.restoreAfterFinish || false;
447 416 this.elementPositioning = this.element.getStyle('position');
448 417
449 - this.originalStyle = {};
418 + this.originalStyle = { };
450 419 ['top','left','width','height','fontSize'].each( function(k) {
451 420 this.originalStyle[k] = this.element.style[k];
452 421 }.bind(this));
@@ -456,7 +425,7
456 425
457 426 var fontSize = this.element.getStyle('font-size') || '100%';
458 427 ['em','px','%','pt'].each( function(fontSizeType) {
459 - if(fontSize.indexOf(fontSizeType)>0) {
428 + if (fontSize.indexOf(fontSizeType)>0) {
460 429 this.fontSize = parseFloat(fontSize);
461 430 this.fontSizeType = fontSizeType;
462 431 }
@@ -465,60 +434,61
465 434 this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
466 435
467 436 this.dims = null;
468 - if(this.options.scaleMode=='box')
437 + if (this.options.scaleMode=='box')
469 438 this.dims = [this.element.offsetHeight, this.element.offsetWidth];
470 - if(/^content/.test(this.options.scaleMode))
439 + if (/^content/.test(this.options.scaleMode))
471 440 this.dims = [this.element.scrollHeight, this.element.scrollWidth];
472 - if(!this.dims)
441 + if (!this.dims)
473 442 this.dims = [this.options.scaleMode.originalHeight,
474 443 this.options.scaleMode.originalWidth];
475 444 },
476 445 update: function(position) {
477 446 var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
478 - if(this.options.scaleContent && this.fontSize)
447 + if (this.options.scaleContent && this.fontSize)
479 448 this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
480 449 this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
481 450 },
482 451 finish: function(position) {
483 - if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
452 + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
484 453 },
485 454 setDimensions: function(height, width) {
486 - var d = {};
487 - if(this.options.scaleX) d.width = Math.round(width) + 'px';
488 - if(this.options.scaleY) d.height = Math.round(height) + 'px';
489 - if(this.options.scaleFromCenter) {
455 + var d = { };
456 + if (this.options.scaleX) d.width = width.round() + 'px';
457 + if (this.options.scaleY) d.height = height.round() + 'px';
458 + if (this.options.scaleFromCenter) {
490 459 var topd = (height - this.dims[0])/2;
491 460 var leftd = (width - this.dims[1])/2;
492 - if(this.elementPositioning == 'absolute') {
493 - if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
494 - if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
461 + if (this.elementPositioning == 'absolute') {
462 + if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
463 + if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
495 464 } else {
496 - if(this.options.scaleY) d.top = -topd + 'px';
497 - if(this.options.scaleX) d.left = -leftd + 'px';
465 + if (this.options.scaleY) d.top = -topd + 'px';
466 + if (this.options.scaleX) d.left = -leftd + 'px';
498 467 }
499 468 }
500 469 this.element.setStyle(d);
501 470 }
502 471 });
503 472
504 - Effect.Highlight = Class.create();
505 - Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
473 + Effect.Highlight = Class.create(Effect.Base, {
506 474 initialize: function(element) {
507 475 this.element = $(element);
508 - if(!this.element) throw(Effect._elementDoesNotExistError);
509 - var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
476 + if (!this.element) throw(Effect._elementDoesNotExistError);
477 + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
510 478 this.start(options);
511 479 },
512 480 setup: function() {
513 481 // Prevent executing on elements not in the layout flow
514 - if(this.element.getStyle('display')=='none') { this.cancel(); return; }
482 + if (this.element.getStyle('display')=='none') { this.cancel(); return; }
515 483 // Disable background image during the effect
516 - this.oldStyle = {
517 - backgroundImage: this.element.getStyle('background-image') };
518 - this.element.setStyle({backgroundImage: 'none'});
519 - if(!this.options.endcolor)
484 + this.oldStyle = { };
485 + if (!this.options.keepBackgroundImage) {
486 + this.oldStyle.backgroundImage = this.element.getStyle('background-image');
487 + this.element.setStyle({backgroundImage: 'none'});
488 + }
489 + if (!this.options.endcolor)
520 490 this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
521 - if(!this.options.restorecolor)
491 + if (!this.options.restorecolor)
522 492 this.options.restorecolor = this.element.getStyle('background-color');
523 493 // init color calculations
524 494 this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
@@ -526,7 +496,7
526 496 },
527 497 update: function(position) {
528 498 this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
529 - return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
499 + return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
530 500 },
531 501 finish: function() {
532 502 this.element.setStyle(Object.extend(this.oldStyle, {
@@ -535,30 +505,21
535 505 }
536 506 });
537 507
538 - Effect.ScrollTo = Class.create();
539 - Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
540 - initialize: function(element) {
541 - this.element = $(element);
542 - this.start(arguments[1] || {});
543 - },
544 - setup: function() {
545 - Position.prepare();
546 - var offsets = Position.cumulativeOffset(this.element);
547 - if(this.options.offset) offsets[1] += this.options.offset;
548 - var max = window.innerHeight ?
549 - window.height - window.innerHeight :
550 - document.body.scrollHeight -
551 - (document.documentElement.clientHeight ?
552 - document.documentElement.clientHeight : document.body.clientHeight);
553 - this.scrollStart = Position.deltaY;
554 - this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
555 - },
556 - update: function(position) {
557 - Position.prepare();
558 - window.scrollTo(Position.deltaX,
559 - this.scrollStart + (position*this.delta));
560 - }
561 - });
508 + Effect.ScrollTo = function(element) {
509 + var options = arguments[1] || { },
510 + scrollOffsets = document.viewport.getScrollOffsets(),
511 + elementOffsets = $(element).cumulativeOffset(),
512 + max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();
513 +
514 + if (options.offset) elementOffsets[1] += options.offset;
515 +
516 + return new Effect.Tween(null,
517 + scrollOffsets.top,
518 + elementOffsets[1] > max ? max : elementOffsets[1],
519 + options,
520 + function(p){ scrollTo(scrollOffsets.left, p.round()) }
521 + );
522 + };
562 523
563 524 /* ------------- combination effects ------------- */
564 525
@@ -566,14 +527,15
566 527 element = $(element);
567 528 var oldOpacity = element.getInlineOpacity();
568 529 var options = Object.extend({
569 - from: element.getOpacity() || 1.0,
570 - to: 0.0,
571 - afterFinishInternal: function(effect) {
572 - if(effect.options.to!=0) return;
573 - effect.element.hide().setStyle({opacity: oldOpacity});
574 - }}, arguments[1] || {});
530 + from: element.getOpacity() || 1.0,
531 + to: 0.0,
532 + afterFinishInternal: function(effect) {
533 + if (effect.options.to!=0) return;
534 + effect.element.hide().setStyle({opacity: oldOpacity});
535 + }
536 + }, arguments[1] || { });
575 537 return new Effect.Opacity(element,options);
576 - }
538 + };
577 539
578 540 Effect.Appear = function(element) {
579 541 element = $(element);
@@ -586,9 +548,9
586 548 },
587 549 beforeSetup: function(effect) {
588 550 effect.element.setOpacity(effect.options.from).show();
589 - }}, arguments[1] || {});
551 + }}, arguments[1] || { });
590 552 return new Effect.Opacity(element,options);
591 - }
553 + };
592 554
593 555 Effect.Puff = function(element) {
594 556 element = $(element);
@@ -610,9 +572,9
610 572 },
611 573 afterFinishInternal: function(effect) {
612 574 effect.effects[0].element.hide().setStyle(oldStyle); }
613 - }, arguments[1] || {})
575 + }, arguments[1] || { })
614 576 );
615 - }
577 + };
616 578
617 579 Effect.BlindUp = function(element) {
618 580 element = $(element);
@@ -624,9 +586,9
624 586 afterFinishInternal: function(effect) {
625 587 effect.element.hide().undoClipping();
626 588 }
627 - }, arguments[1] || {})
589 + }, arguments[1] || { })
628 590 );
629 - }
591 + };
630 592
631 593 Effect.BlindDown = function(element) {
632 594 element = $(element);
@@ -643,8 +605,8
643 605 afterFinishInternal: function(effect) {
644 606 effect.element.undoClipping();
645 607 }
646 - }, arguments[1] || {}));
647 - }
608 + }, arguments[1] || { }));
609 + };
648 610
649 611 Effect.SwitchOff = function(element) {
650 612 element = $(element);
@@ -665,8 +627,8
665 627 }
666 628 })
667 629 }
668 - }, arguments[1] || {}));
669 - }
630 + }, arguments[1] || { }));
631 + };
670 632
671 633 Effect.DropOut = function(element) {
672 634 element = $(element);
@@ -685,29 +647,35
685 647 afterFinishInternal: function(effect) {
686 648 effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
687 649 }
688 - }, arguments[1] || {}));
689 - }
650 + }, arguments[1] || { }));
651 + };
690 652
691 653 Effect.Shake = function(element) {
692 654 element = $(element);
655 + var options = Object.extend({
656 + distance: 20,
657 + duration: 0.5
658 + }, arguments[1] || {});
659 + var distance = parseFloat(options.distance);
660 + var split = parseFloat(options.duration) / 10.0;
693 661 var oldStyle = {
694 662 top: element.getStyle('top'),
695 663 left: element.getStyle('left') };
696 - return new Effect.Move(element,
697 - { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
664 + return new Effect.Move(element,
665 + { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) {
698 666 new Effect.Move(effect.element,
699 - { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
667 + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
700 668 new Effect.Move(effect.element,
701 - { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
669 + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
702 670 new Effect.Move(effect.element,
703 - { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
671 + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
704 672 new Effect.Move(effect.element,
705 - { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
673 + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
706 674 new Effect.Move(effect.element,
707 - { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
675 + { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
708 676 effect.element.undoPositioned().setStyle(oldStyle);
709 677 }}) }}) }}) }}) }}) }});
710 - }
678 + };
711 679
712 680 Effect.SlideDown = function(element) {
713 681 element = $(element).cleanWhitespace();
@@ -723,7 +691,7
723 691 afterSetup: function(effect) {
724 692 effect.element.makePositioned();
725 693 effect.element.down().makePositioned();
726 - if(window.opera) effect.element.setStyle({top: ''});
694 + if (window.opera) effect.element.setStyle({top: ''});
727 695 effect.element.makeClipping().setStyle({height: '0px'}).show();
728 696 },
729 697 afterUpdateInternal: function(effect) {
@@ -733,23 +701,25
733 701 afterFinishInternal: function(effect) {
734 702 effect.element.undoClipping().undoPositioned();
735 703 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
736 - }, arguments[1] || {})
704 + }, arguments[1] || { })
737 705 );
738 - }
706 + };
739 707
740 708 Effect.SlideUp = function(element) {
741 709 element = $(element).cleanWhitespace();
742 710 var oldInnerBottom = element.down().getStyle('bottom');
711 + var elementDimensions = element.getDimensions();
743 712 return new Effect.Scale(element, window.opera ? 0 : 1,
744 713 Object.extend({ scaleContent: false,
745 714 scaleX: false,
746 715 scaleMode: 'box',
747 716 scaleFrom: 100,
717 + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
748 718 restoreAfterFinish: true,
749 - beforeStartInternal: function(effect) {
719 + afterSetup: function(effect) {
750 720 effect.element.makePositioned();
751 721 effect.element.down().makePositioned();
752 - if(window.opera) effect.element.setStyle({top: ''});
722 + if (window.opera) effect.element.setStyle({top: ''});
753 723 effect.element.makeClipping().show();
754 724 },
755 725 afterUpdateInternal: function(effect) {
@@ -757,12 +727,12
757 727 (effect.dims[0] - effect.element.clientHeight) + 'px' });
758 728 },
759 729 afterFinishInternal: function(effect) {
760 - effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom});
761 - effect.element.down().undoPositioned();
730 + effect.element.hide().undoClipping().undoPositioned();
731 + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
762 732 }
763 - }, arguments[1] || {})
733 + }, arguments[1] || { })
764 734 );
765 - }
735 + };
766 736
767 737 // Bug in opera makes the TD containing this element expand for a instance after finish
768 738 Effect.Squish = function(element) {
@@ -775,7 +745,7
775 745 effect.element.hide().undoClipping();
776 746 }
777 747 });
778 - }
748 + };
779 749
780 750 Effect.Grow = function(element) {
781 751 element = $(element);
@@ -784,7 +754,7
784 754 moveTransition: Effect.Transitions.sinoidal,
785 755 scaleTransition: Effect.Transitions.sinoidal,
786 756 opacityTransition: Effect.Transitions.full
787 - }, arguments[1] || {});
757 + }, arguments[1] || { });
788 758 var oldStyle = {
789 759 top: element.style.top,
790 760 left: element.style.left,
@@ -849,7 +819,7
849 819 )
850 820 }
851 821 });
852 - }
822 + };
853 823
854 824 Effect.Shrink = function(element) {
855 825 element = $(element);
@@ -858,7 +828,7
858 828 moveTransition: Effect.Transitions.sinoidal,
859 829 scaleTransition: Effect.Transitions.sinoidal,
860 830 opacityTransition: Effect.Transitions.none
861 - }, arguments[1] || {});
831 + }, arguments[1] || { });
862 832 var oldStyle = {
863 833 top: element.style.top,
864 834 left: element.style.left,
@@ -903,11 +873,11
903 873 effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
904 874 }, options)
905 875 );
906 - }
876 + };
907 877
908 878 Effect.Pulsate = function(element) {
909 879 element = $(element);
910 - var options = arguments[1] || {};
880 + var options = arguments[1] || { };
911 881 var oldOpacity = element.getInlineOpacity();
912 882 var transition = options.transition || Effect.Transitions.sinoidal;
913 883 var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
@@ -916,7 +886,7
916 886 Object.extend(Object.extend({ duration: 2.0, from: 0,
917 887 afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
918 888 }, options), {transition: reverser}));
919 - }
889 + };
920 890
921 891 Effect.Fold = function(element) {
922 892 element = $(element);
@@ -936,37 +906,71
936 906 afterFinishInternal: function(effect) {
937 907 effect.element.hide().undoClipping().setStyle(oldStyle);
938 908 } });
939 - }}, arguments[1] || {}));
909 + }}, arguments[1] || { }));
940 910 };
941 911
942 - Effect.Morph = Class.create();
943 - Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
912 + Effect.Morph = Class.create(Effect.Base, {
944 913 initialize: function(element) {
945 914 this.element = $(element);
946 - if(!this.element) throw(Effect._elementDoesNotExistError);
915 + if (!this.element) throw(Effect._elementDoesNotExistError);
947 916 var options = Object.extend({
948 - style: ''
949 - }, arguments[1] || {});
917 + style: { }
918 + }, arguments[1] || { });
919 +
920 + if (!Object.isString(options.style)) this.style = $H(options.style);
921 + else {
922 + if (options.style.include(':'))
923 + this.style = options.style.parseStyle();
924 + else {
925 + this.element.addClassName(options.style);
926 + this.style = $H(this.element.getStyles());
927 + this.element.removeClassName(options.style);
928 + var css = this.element.getStyles();
929 + this.style = this.style.reject(function(style) {
930 + return style.value == css[style.key];
931 + });
932 + options.afterFinishInternal = function(effect) {
933 + effect.element.addClassName(effect.options.style);
934 + effect.transforms.each(function(transform) {
935 + effect.element.style[transform.style] = '';
936 + });
937 + }
938 + }
939 + }
950 940 this.start(options);
951 941 },
942 +
952 943 setup: function(){
953 944 function parseColor(color){
954 - if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
945 + if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
955 946 color = color.parseColor();
956 947 return $R(0,2).map(function(i){
957 948 return parseInt( color.slice(i*2+1,i*2+3), 16 )
958 949 });
959 950 }
960 - this.transforms = this.options.style.parseStyle().map(function(property){
961 - var originalValue = this.element.getStyle(property[0]);
962 - return $H({
963 - style: property[0],
964 - originalValue: property[1].unit=='color' ?
965 - parseColor(originalValue) : parseFloat(originalValue || 0),
966 - targetValue: property[1].unit=='color' ?
967 - parseColor(property[1].value) : property[1].value,
968 - unit: property[1].unit
969 - });
951 + this.transforms = this.style.map(function(pair){
952 + var property = pair[0], value = pair[1], unit = null;
953 +
954 + if (value.parseColor('#zzzzzz') != '#zzzzzz') {
955 + value = value.parseColor();
956 + unit = 'color';
957 + } else if (property == 'opacity') {
958 + value = parseFloat(value);
959 + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
960 + this.element.setStyle({zoom: 1});
961 + } else if (Element.CSS_LENGTH.test(value)) {
962 + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
963 + value = parseFloat(components[1]);
964 + unit = (components.length == 3) ? components[2] : null;
965 + }
966 +
967 + var originalValue = this.element.getStyle(property);
968 + return {
969 + style: property.camelize(),
970 + originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
971 + targetValue: unit=='color' ? parseColor(value) : value,
972 + unit: unit
973 + };
970 974 }.bind(this)).reject(function(transform){
971 975 return (
972 976 (transform.originalValue == transform.targetValue) ||
@@ -978,32 +982,35
978 982 });
979 983 },
980 984 update: function(position) {
981 - var style = $H(), value = null;
982 - this.transforms.each(function(transform){
983 - value = transform.unit=='color' ?
984 - $R(0,2).inject('#',function(m,v,i){
985 - return m+(Math.round(transform.originalValue[i]+
986 - (transform.targetValue[i] - transform.originalValue[i])*position)).toColorPart() }) :
987 - transform.originalValue + Math.round(
988 - ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
989 - style[transform.style] = value;
990 - });
991 - this.element.setStyle(style);
985 + var style = { }, transform, i = this.transforms.length;
986 + while(i--)
987 + style[(transform = this.transforms[i]).style] =
988 + transform.unit=='color' ? '#'+
989 + (Math.round(transform.originalValue[0]+
990 + (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
991 + (Math.round(transform.originalValue[1]+
992 + (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
993 + (Math.round(transform.originalValue[2]+
994 + (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
995 + (transform.originalValue +
996 + (transform.targetValue - transform.originalValue) * position).toFixed(3) +
997 + (transform.unit === null ? '' : transform.unit);
998 + this.element.setStyle(style, true);
992 999 }
993 1000 });
994 1001
995 - Effect.Transform = Class.create();
996 - Object.extend(Effect.Transform.prototype, {
1002 + Effect.Transform = Class.create({
997 1003 initialize: function(tracks){
998 1004 this.tracks = [];
999 - this.options = arguments[1] || {};
1005 + this.options = arguments[1] || { };
1000 1006 this.addTracks(tracks);
1001 1007 },
1002 1008 addTracks: function(tracks){
1003 1009 tracks.each(function(track){
1004 - var data = $H(track).values().first();
1010 + track = $H(track);
1011 + var data = track.values().first();
1005 1012 this.tracks.push($H({
1006 - ids: $H(track).keys().first(),
1013 + ids: track.keys().first(),
1007 1014 effect: Effect.Morph,
1008 1015 options: { style: data }
1009 1016 }));
@@ -1013,76 +1020,101
1013 1020 play: function(){
1014 1021 return new Effect.Parallel(
1015 1022 this.tracks.map(function(track){
1016 - var elements = [$(track.ids) || $$(track.ids)].flatten();
1017 - return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
1023 + var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
1024 + var elements = [$(ids) || $$(ids)].flatten();
1025 + return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
1018 1026 }).flatten(),
1019 1027 this.options
1020 1028 );
1021 1029 }
1022 1030 });
1023 1031
1024 - Element.CSS_PROPERTIES = ['azimuth', 'backgroundAttachment', 'backgroundColor', 'backgroundImage',
1025 - 'backgroundPosition', 'backgroundRepeat', 'borderBottomColor', 'borderBottomStyle',
1026 - 'borderBottomWidth', 'borderCollapse', 'borderLeftColor', 'borderLeftStyle', 'borderLeftWidth',
1027 - 'borderRightColor', 'borderRightStyle', 'borderRightWidth', 'borderSpacing', 'borderTopColor',
1028 - 'borderTopStyle', 'borderTopWidth', 'bottom', 'captionSide', 'clear', 'clip', 'color', 'content',
1029 - 'counterIncrement', 'counterReset', 'cssFloat', 'cueAfter', 'cueBefore', 'cursor', 'direction',
1030 - 'display', 'elevation', 'emptyCells', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch',
1031 - 'fontStyle', 'fontVariant', 'fontWeight', 'height', 'left', 'letterSpacing', 'lineHeight',
1032 - 'listStyleImage', 'listStylePosition', 'listStyleType', 'marginBottom', 'marginLeft', 'marginRight',
1033 - 'marginTop', 'markerOffset', 'marks', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'opacity',
1034 - 'orphans', 'outlineColor', 'outlineOffset', 'outlineStyle', 'outlineWidth', 'overflowX', 'overflowY',
1035 - 'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop', 'page', 'pageBreakAfter', 'pageBreakBefore',
1036 - 'pageBreakInside', 'pauseAfter', 'pauseBefore', 'pitch', 'pitchRange', 'position', 'quotes',
1037 - 'richness', 'right', 'size', 'speakHeader', 'speakNumeral', 'speakPunctuation', 'speechRate', 'stress',
1038 - 'tableLayout', 'textAlign', 'textDecoration', 'textIndent', 'textShadow', 'textTransform', 'top',
1039 - 'unicodeBidi', 'verticalAlign', 'visibility', 'voiceFamily', 'volume', 'whiteSpace', 'widows',
1040 - 'width', 'wordSpacing', 'zIndex'];
1032 + Element.CSS_PROPERTIES = $w(
1033 + 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
1034 + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
1035 + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
1036 + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
1037 + 'fontSize fontWeight height left letterSpacing lineHeight ' +
1038 + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
1039 + 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
1040 + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
1041 + 'right textIndent top width wordSpacing zIndex');
1041 1042
1042 1043 Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1043 1044
1045 + String.__parseStyleElement = document.createElement('div');
1044 1046 String.prototype.parseStyle = function(){
1045 - var element = Element.extend(document.createElement('div'));
1046 - element.innerHTML = '<div style="' + this + '"></div>';
1047 - var style = element.down().style, styleRules = $H();
1047 + var style, styleRules = $H();
1048 + if (Prototype.Browser.WebKit)
1049 + style = new Element('div',{style:this}).style;
1050 + else {
1051 + String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
1052 + style = String.__parseStyleElement.childNodes[0].style;
1053 + }
1048 1054
1049 1055 Element.CSS_PROPERTIES.each(function(property){
1050 - if(style[property]) styleRules[property] = style[property];
1056 + if (style[property]) styleRules.set(property, style[property]);
1051 1057 });
1052 1058
1053 - var result = $H();
1054 -
1055 - styleRules.each(function(pair){
1056 - var property = pair[0], value = pair[1], unit = null;
1057 -
1058 - if(value.parseColor('#zzzzzz') != '#zzzzzz') {
1059 - value = value.parseColor();
1060 - unit = 'color';
1061 - } else if(Element.CSS_LENGTH.test(value))
1062 - var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/),
1063 - value = parseFloat(components[1]), unit = (components.length == 3) ? components[2] : null;
1064 -
1065 - result[property.underscore().dasherize()] = $H({ value:value, unit:unit });
1066 - }.bind(this));
1067 -
1068 - return result;
1059 + if (Prototype.Browser.IE && this.include('opacity'))
1060 + styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
1061 +
1062 + return styleRules;
1063 + };
1064 +
1065 + if (document.defaultView && document.defaultView.getComputedStyle) {
1066 + Element.getStyles = function(element) {
1067 + var css = document.defaultView.getComputedStyle($(element), null);
1068 + return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
1069 + styles[property] = css[property];
1070 + return styles;
1071 + });
1072 + };
1073 + } else {
1074 + Element.getStyles = function(element) {
1075 + element = $(element);
1076 + var css = element.currentStyle, styles;
1077 + styles = Element.CSS_PROPERTIES.inject({ }, function(hash, property) {
1078 + hash.set(property, css[property]);
1079 + return hash;
1080 + });
1081 + if (!styles.opacity) styles.set('opacity', element.getOpacity());
1082 + return styles;
1083 + };
1069 1084 };
1070 1085
1071 - Element.morph = function(element, style) {
1072 - new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
1073 - return element;
1086 + Effect.Methods = {
1087 + morph: function(element, style) {
1088 + element = $(element);
1089 + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
1090 + return element;
1091 + },
1092 + visualEffect: function(element, effect, options) {
1093 + element = $(element)
1094 + var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
1095 + new Effect[klass](element, options);
1096 + return element;
1097 + },
1098 + highlight: function(element, options) {
1099 + element = $(element);
1100 + new Effect.Highlight(element, options);
1101 + return element;
1102 + }
1074 1103 };
1075 1104
1076 - ['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
1077 - 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each(
1078 - function(f) { Element.Methods[f] = Element[f]; }
1105 + $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
1106 + 'pulsate shake puff squish switchOff dropOut').each(
1107 + function(effect) {
1108 + Effect.Methods[effect] = function(element, options){
1109 + element = $(element);
1110 + Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
1111 + return element;
1112 + }
1113 + }
1079 1114 );
1080 1115
1081 - Element.Methods.visualEffect = function(element, effect, options) {
1082 - s = effect.gsub(/_/, '-').camelize();
1083 - effect_class = s.charAt(0).toUpperCase() + s.substring(1);
1084 - new Effect[effect_class](element, options);
1085 - return $(element);
1086 - };
1116 + $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
1117 + function(f) { Effect.Methods[f] = Element[f]; }
1118 + );
1087 1119
1088 - Element.addMethods(); No newline at end of file
1120 + Element.addMethods(Effect.Methods);
You need to be logged in to leave comments. Login now