##// END OF EJS Templates
Upgraded to Prototype 1.6.0.1....
Jean-Philippe Lang -
r1580:75a5dbd01dd4
parent child
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,218 +1,218
1 /* redMine - project management software
1 /* redMine - project management software
2 Copyright (C) 2006-2008 Jean-Philippe Lang */
2 Copyright (C) 2006-2008 Jean-Philippe Lang */
3
3
4 var observingContextMenuClick;
4 var observingContextMenuClick;
5
5
6 ContextMenu = Class.create();
6 ContextMenu = Class.create();
7 ContextMenu.prototype = {
7 ContextMenu.prototype = {
8 initialize: function (url) {
8 initialize: function (url) {
9 this.url = url;
9 this.url = url;
10
10
11 // prevent selection when using Ctrl/Shit key
11 // prevent selection when using Ctrl/Shit key
12 var tables = $$('table.issues');
12 var tables = $$('table.issues');
13 for (i=0; i<tables.length; i++) {
13 for (i=0; i<tables.length; i++) {
14 tables[i].onselectstart = function () { return false; } // ie
14 tables[i].onselectstart = function () { return false; } // ie
15 tables[i].onmousedown = function () { return false; } // mozilla
15 tables[i].onmousedown = function () { return false; } // mozilla
16 }
16 }
17
17
18 if (!observingContextMenuClick) {
18 if (!observingContextMenuClick) {
19 Event.observe(document, 'click', this.Click.bindAsEventListener(this));
19 Event.observe(document, 'click', this.Click.bindAsEventListener(this));
20 Event.observe(document, (window.opera ? 'click' : 'contextmenu'), this.RightClick.bindAsEventListener(this));
20 Event.observe(document, (window.opera ? 'click' : 'contextmenu'), this.RightClick.bindAsEventListener(this));
21 observingContextMenuClick = true;
21 observingContextMenuClick = true;
22 }
22 }
23
23
24 this.unselectAll();
24 this.unselectAll();
25 this.lastSelected = null;
25 this.lastSelected = null;
26 },
26 },
27
27
28 RightClick: function(e) {
28 RightClick: function(e) {
29 this.hideMenu();
29 this.hideMenu();
30 // do not show the context menu on links
30 // do not show the context menu on links
31 if (Event.findElement(e, 'a') != document) { return; }
31 if (Event.findElement(e, 'a') != document && Event.findElement(e, 'a') != undefined) { return; }
32 // right-click simulated by Alt+Click with Opera
32 // right-click simulated by Alt+Click with Opera
33 if (window.opera && !e.altKey) { return; }
33 if (window.opera && !e.altKey) { return; }
34 var tr = Event.findElement(e, 'tr');
34 var tr = Event.findElement(e, 'tr');
35 if ((tr == document) || !tr.hasClassName('hascontextmenu')) { return; }
35 if (tr == document || tr == undefined || !tr.hasClassName('hascontextmenu')) { return; }
36 Event.stop(e);
36 Event.stop(e);
37 if (!this.isSelected(tr)) {
37 if (!this.isSelected(tr)) {
38 this.unselectAll();
38 this.unselectAll();
39 this.addSelection(tr);
39 this.addSelection(tr);
40 this.lastSelected = tr;
40 this.lastSelected = tr;
41 }
41 }
42 this.showMenu(e);
42 this.showMenu(e);
43 },
43 },
44
44
45 Click: function(e) {
45 Click: function(e) {
46 this.hideMenu();
46 this.hideMenu();
47 if (Event.findElement(e, 'a') != document) { return; }
47 if (Event.findElement(e, 'a') != document && Event.findElement(e, 'a') != undefined ) { return; }
48 if (window.opera && e.altKey) { return; }
48 if (window.opera && e.altKey) { return; }
49 if (Event.isLeftClick(e) || (navigator.appVersion.match(/\bMSIE\b/))) {
49 if (Event.isLeftClick(e) || (navigator.appVersion.match(/\bMSIE\b/))) {
50 var tr = Event.findElement(e, 'tr');
50 var tr = Event.findElement(e, 'tr');
51 if (tr!=document && tr.hasClassName('hascontextmenu')) {
51 if (tr!=document && tr.hasClassName('hascontextmenu')) {
52 // a row was clicked, check if the click was on checkbox
52 // a row was clicked, check if the click was on checkbox
53 var box = Event.findElement(e, 'input');
53 var box = Event.findElement(e, 'input');
54 if (box!=document) {
54 if (box!=document && box!=undefined) {
55 // a checkbox may be clicked
55 // a checkbox may be clicked
56 if (box.checked) {
56 if (box.checked) {
57 tr.addClassName('context-menu-selection');
57 tr.addClassName('context-menu-selection');
58 } else {
58 } else {
59 tr.removeClassName('context-menu-selection');
59 tr.removeClassName('context-menu-selection');
60 }
60 }
61 } else {
61 } else {
62 if (e.ctrlKey) {
62 if (e.ctrlKey) {
63 this.toggleSelection(tr);
63 this.toggleSelection(tr);
64 } else if (e.shiftKey) {
64 } else if (e.shiftKey) {
65 if (this.lastSelected != null) {
65 if (this.lastSelected != null) {
66 var toggling = false;
66 var toggling = false;
67 var rows = $$('.hascontextmenu');
67 var rows = $$('.hascontextmenu');
68 for (i=0; i<rows.length; i++) {
68 for (i=0; i<rows.length; i++) {
69 if (toggling || rows[i]==tr) {
69 if (toggling || rows[i]==tr) {
70 this.addSelection(rows[i]);
70 this.addSelection(rows[i]);
71 }
71 }
72 if (rows[i]==tr || rows[i]==this.lastSelected) {
72 if (rows[i]==tr || rows[i]==this.lastSelected) {
73 toggling = !toggling;
73 toggling = !toggling;
74 }
74 }
75 }
75 }
76 } else {
76 } else {
77 this.addSelection(tr);
77 this.addSelection(tr);
78 }
78 }
79 } else {
79 } else {
80 this.unselectAll();
80 this.unselectAll();
81 this.addSelection(tr);
81 this.addSelection(tr);
82 }
82 }
83 this.lastSelected = tr;
83 this.lastSelected = tr;
84 }
84 }
85 } else {
85 } else {
86 // click is outside the rows
86 // click is outside the rows
87 var t = Event.findElement(e, 'a');
87 var t = Event.findElement(e, 'a');
88 if ((t != document) && (Element.hasClassName(t, 'disabled') || Element.hasClassName(t, 'submenu'))) {
88 if ((t != document) && (Element.hasClassName(t, 'disabled') || Element.hasClassName(t, 'submenu'))) {
89 Event.stop(e);
89 Event.stop(e);
90 }
90 }
91 }
91 }
92 }
92 }
93 },
93 },
94
94
95 showMenu: function(e) {
95 showMenu: function(e) {
96 var mouse_x = Event.pointerX(e);
96 var mouse_x = Event.pointerX(e);
97 var mouse_y = Event.pointerY(e);
97 var mouse_y = Event.pointerY(e);
98 var render_x = mouse_x;
98 var render_x = mouse_x;
99 var render_y = mouse_y;
99 var render_y = mouse_y;
100 var dims;
100 var dims;
101 var menu_width;
101 var menu_width;
102 var menu_height;
102 var menu_height;
103 var window_width;
103 var window_width;
104 var window_height;
104 var window_height;
105 var max_width;
105 var max_width;
106 var max_height;
106 var max_height;
107
107
108 $('context-menu').style['left'] = (render_x + 'px');
108 $('context-menu').style['left'] = (render_x + 'px');
109 $('context-menu').style['top'] = (render_y + 'px');
109 $('context-menu').style['top'] = (render_y + 'px');
110 Element.update('context-menu', '');
110 Element.update('context-menu', '');
111
111
112 new Ajax.Updater({success:'context-menu'}, this.url,
112 new Ajax.Updater({success:'context-menu'}, this.url,
113 {asynchronous:true,
113 {asynchronous:true,
114 evalScripts:true,
114 evalScripts:true,
115 parameters:Form.serialize(Event.findElement(e, 'form')),
115 parameters:Form.serialize(Event.findElement(e, 'form')),
116 onComplete:function(request){
116 onComplete:function(request){
117 dims = $('context-menu').getDimensions();
117 dims = $('context-menu').getDimensions();
118 menu_width = dims.width;
118 menu_width = dims.width;
119 menu_height = dims.height;
119 menu_height = dims.height;
120 max_width = mouse_x + 2*menu_width;
120 max_width = mouse_x + 2*menu_width;
121 max_height = mouse_y + menu_height;
121 max_height = mouse_y + menu_height;
122
122
123 var ws = window_size();
123 var ws = window_size();
124 window_width = ws.width;
124 window_width = ws.width;
125 window_height = ws.height;
125 window_height = ws.height;
126
126
127 /* display the menu above and/or to the left of the click if needed */
127 /* display the menu above and/or to the left of the click if needed */
128 if (max_width > window_width) {
128 if (max_width > window_width) {
129 render_x -= menu_width;
129 render_x -= menu_width;
130 $('context-menu').addClassName('reverse-x');
130 $('context-menu').addClassName('reverse-x');
131 } else {
131 } else {
132 $('context-menu').removeClassName('reverse-x');
132 $('context-menu').removeClassName('reverse-x');
133 }
133 }
134 if (max_height > window_height) {
134 if (max_height > window_height) {
135 render_y -= menu_height;
135 render_y -= menu_height;
136 $('context-menu').addClassName('reverse-y');
136 $('context-menu').addClassName('reverse-y');
137 } else {
137 } else {
138 $('context-menu').removeClassName('reverse-y');
138 $('context-menu').removeClassName('reverse-y');
139 }
139 }
140 if (render_x <= 0) render_x = 1;
140 if (render_x <= 0) render_x = 1;
141 if (render_y <= 0) render_y = 1;
141 if (render_y <= 0) render_y = 1;
142 $('context-menu').style['left'] = (render_x + 'px');
142 $('context-menu').style['left'] = (render_x + 'px');
143 $('context-menu').style['top'] = (render_y + 'px');
143 $('context-menu').style['top'] = (render_y + 'px');
144
144
145 Effect.Appear('context-menu', {duration: 0.20});
145 Effect.Appear('context-menu', {duration: 0.20});
146 if (window.parseStylesheets) { window.parseStylesheets(); } // IE
146 if (window.parseStylesheets) { window.parseStylesheets(); } // IE
147 }})
147 }})
148 },
148 },
149
149
150 hideMenu: function() {
150 hideMenu: function() {
151 Element.hide('context-menu');
151 Element.hide('context-menu');
152 },
152 },
153
153
154 addSelection: function(tr) {
154 addSelection: function(tr) {
155 tr.addClassName('context-menu-selection');
155 tr.addClassName('context-menu-selection');
156 this.checkSelectionBox(tr, true);
156 this.checkSelectionBox(tr, true);
157 },
157 },
158
158
159 toggleSelection: function(tr) {
159 toggleSelection: function(tr) {
160 if (this.isSelected(tr)) {
160 if (this.isSelected(tr)) {
161 this.removeSelection(tr);
161 this.removeSelection(tr);
162 } else {
162 } else {
163 this.addSelection(tr);
163 this.addSelection(tr);
164 }
164 }
165 },
165 },
166
166
167 removeSelection: function(tr) {
167 removeSelection: function(tr) {
168 tr.removeClassName('context-menu-selection');
168 tr.removeClassName('context-menu-selection');
169 this.checkSelectionBox(tr, false);
169 this.checkSelectionBox(tr, false);
170 },
170 },
171
171
172 unselectAll: function() {
172 unselectAll: function() {
173 var rows = $$('.hascontextmenu');
173 var rows = $$('.hascontextmenu');
174 for (i=0; i<rows.length; i++) {
174 for (i=0; i<rows.length; i++) {
175 this.removeSelection(rows[i]);
175 this.removeSelection(rows[i]);
176 }
176 }
177 },
177 },
178
178
179 checkSelectionBox: function(tr, checked) {
179 checkSelectionBox: function(tr, checked) {
180 var inputs = Element.getElementsBySelector(tr, 'input');
180 var inputs = Element.getElementsBySelector(tr, 'input');
181 if (inputs.length > 0) { inputs[0].checked = checked; }
181 if (inputs.length > 0) { inputs[0].checked = checked; }
182 },
182 },
183
183
184 isSelected: function(tr) {
184 isSelected: function(tr) {
185 return Element.hasClassName(tr, 'context-menu-selection');
185 return Element.hasClassName(tr, 'context-menu-selection');
186 }
186 }
187 }
187 }
188
188
189 function toggleIssuesSelection(el) {
189 function toggleIssuesSelection(el) {
190 var boxes = el.getElementsBySelector('input[type=checkbox]');
190 var boxes = el.getElementsBySelector('input[type=checkbox]');
191 var all_checked = true;
191 var all_checked = true;
192 for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } }
192 for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } }
193 for (i = 0; i < boxes.length; i++) {
193 for (i = 0; i < boxes.length; i++) {
194 if (all_checked) {
194 if (all_checked) {
195 boxes[i].checked = false;
195 boxes[i].checked = false;
196 boxes[i].up('tr').removeClassName('context-menu-selection');
196 boxes[i].up('tr').removeClassName('context-menu-selection');
197 } else if (boxes[i].checked == false) {
197 } else if (boxes[i].checked == false) {
198 boxes[i].checked = true;
198 boxes[i].checked = true;
199 boxes[i].up('tr').addClassName('context-menu-selection');
199 boxes[i].up('tr').addClassName('context-menu-selection');
200 }
200 }
201 }
201 }
202 }
202 }
203
203
204 function window_size() {
204 function window_size() {
205 var w;
205 var w;
206 var h;
206 var h;
207 if (window.innerWidth) {
207 if (window.innerWidth) {
208 w = window.innerWidth;
208 w = window.innerWidth;
209 h = window.innerHeight;
209 h = window.innerHeight;
210 } else if (document.documentElement) {
210 } else if (document.documentElement) {
211 w = document.documentElement.clientWidth;
211 w = document.documentElement.clientWidth;
212 h = document.documentElement.clientHeight;
212 h = document.documentElement.clientHeight;
213 } else {
213 } else {
214 w = document.body.clientWidth;
214 w = document.body.clientWidth;
215 h = document.body.clientHeight;
215 h = document.body.clientHeight;
216 }
216 }
217 return {width: w, height: h};
217 return {width: w, height: h};
218 }
218 }
This diff has been collapsed as it changes many lines, (838 lines changed) Show them Hide them
@@ -1,833 +1,963
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 // (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
2 // (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3 // (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com)
3 // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
4 // Contributors:
4 // Contributors:
5 // Richard Livsey
5 // Richard Livsey
6 // Rahul Bhargava
6 // Rahul Bhargava
7 // Rob Wills
7 // Rob Wills
8 //
8 //
9 // script.aculo.us is freely distributable under the terms of an MIT-style license.
9 // script.aculo.us is freely distributable under the terms of an MIT-style license.
10 // For details, see the script.aculo.us web site: http://script.aculo.us/
10 // For details, see the script.aculo.us web site: http://script.aculo.us/
11
11
12 // Autocompleter.Base handles all the autocompletion functionality
12 // Autocompleter.Base handles all the autocompletion functionality
13 // that's independent of the data source for autocompletion. This
13 // that's independent of the data source for autocompletion. This
14 // includes drawing the autocompletion menu, observing keyboard
14 // includes drawing the autocompletion menu, observing keyboard
15 // and mouse events, and similar.
15 // and mouse events, and similar.
16 //
16 //
17 // Specific autocompleters need to provide, at the very least,
17 // Specific autocompleters need to provide, at the very least,
18 // a getUpdatedChoices function that will be invoked every time
18 // a getUpdatedChoices function that will be invoked every time
19 // the text inside the monitored textbox changes. This method
19 // the text inside the monitored textbox changes. This method
20 // should get the text for which to provide autocompletion by
20 // should get the text for which to provide autocompletion by
21 // invoking this.getToken(), NOT by directly accessing
21 // invoking this.getToken(), NOT by directly accessing
22 // this.element.value. This is to allow incremental tokenized
22 // this.element.value. This is to allow incremental tokenized
23 // autocompletion. Specific auto-completion logic (AJAX, etc)
23 // autocompletion. Specific auto-completion logic (AJAX, etc)
24 // belongs in getUpdatedChoices.
24 // belongs in getUpdatedChoices.
25 //
25 //
26 // Tokenized incremental autocompletion is enabled automatically
26 // Tokenized incremental autocompletion is enabled automatically
27 // when an autocompleter is instantiated with the 'tokens' option
27 // when an autocompleter is instantiated with the 'tokens' option
28 // in the options parameter, e.g.:
28 // in the options parameter, e.g.:
29 // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
29 // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
30 // will incrementally autocomplete with a comma as the token.
30 // will incrementally autocomplete with a comma as the token.
31 // Additionally, ',' in the above example can be replaced with
31 // Additionally, ',' in the above example can be replaced with
32 // a token array, e.g. { tokens: [',', '\n'] } which
32 // a token array, e.g. { tokens: [',', '\n'] } which
33 // enables autocompletion on multiple tokens. This is most
33 // enables autocompletion on multiple tokens. This is most
34 // useful when one of the tokens is \n (a newline), as it
34 // useful when one of the tokens is \n (a newline), as it
35 // allows smart autocompletion after linebreaks.
35 // allows smart autocompletion after linebreaks.
36
36
37 if(typeof Effect == 'undefined')
37 if(typeof Effect == 'undefined')
38 throw("controls.js requires including script.aculo.us' effects.js library");
38 throw("controls.js requires including script.aculo.us' effects.js library");
39
39
40 var Autocompleter = {}
40 var Autocompleter = { }
41 Autocompleter.Base = function() {};
41 Autocompleter.Base = Class.create({
42 Autocompleter.Base.prototype = {
43 baseInitialize: function(element, update, options) {
42 baseInitialize: function(element, update, options) {
44 this.element = $(element);
43 element = $(element)
44 this.element = element;
45 this.update = $(update);
45 this.update = $(update);
46 this.hasFocus = false;
46 this.hasFocus = false;
47 this.changed = false;
47 this.changed = false;
48 this.active = false;
48 this.active = false;
49 this.index = 0;
49 this.index = 0;
50 this.entryCount = 0;
50 this.entryCount = 0;
51 this.oldElementValue = this.element.value;
51
52
52 if(this.setOptions)
53 if(this.setOptions)
53 this.setOptions(options);
54 this.setOptions(options);
54 else
55 else
55 this.options = options || {};
56 this.options = options || { };
56
57
57 this.options.paramName = this.options.paramName || this.element.name;
58 this.options.paramName = this.options.paramName || this.element.name;
58 this.options.tokens = this.options.tokens || [];
59 this.options.tokens = this.options.tokens || [];
59 this.options.frequency = this.options.frequency || 0.4;
60 this.options.frequency = this.options.frequency || 0.4;
60 this.options.minChars = this.options.minChars || 1;
61 this.options.minChars = this.options.minChars || 1;
61 this.options.onShow = this.options.onShow ||
62 this.options.onShow = this.options.onShow ||
62 function(element, update){
63 function(element, update){
63 if(!update.style.position || update.style.position=='absolute') {
64 if(!update.style.position || update.style.position=='absolute') {
64 update.style.position = 'absolute';
65 update.style.position = 'absolute';
65 Position.clone(element, update, {
66 Position.clone(element, update, {
66 setHeight: false,
67 setHeight: false,
67 offsetTop: element.offsetHeight
68 offsetTop: element.offsetHeight
68 });
69 });
69 }
70 }
70 Effect.Appear(update,{duration:0.15});
71 Effect.Appear(update,{duration:0.15});
71 };
72 };
72 this.options.onHide = this.options.onHide ||
73 this.options.onHide = this.options.onHide ||
73 function(element, update){ new Effect.Fade(update,{duration:0.15}) };
74 function(element, update){ new Effect.Fade(update,{duration:0.15}) };
74
75
75 if(typeof(this.options.tokens) == 'string')
76 if(typeof(this.options.tokens) == 'string')
76 this.options.tokens = new Array(this.options.tokens);
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 this.observer = null;
82 this.observer = null;
79
83
80 this.element.setAttribute('autocomplete','off');
84 this.element.setAttribute('autocomplete','off');
81
85
82 Element.hide(this.update);
86 Element.hide(this.update);
83
87
84 Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
88 Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
85 Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
89 Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
86 },
90 },
87
91
88 show: function() {
92 show: function() {
89 if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
93 if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
90 if(!this.iefix &&
94 if(!this.iefix &&
91 (navigator.appVersion.indexOf('MSIE')>0) &&
95 (Prototype.Browser.IE) &&
92 (navigator.userAgent.indexOf('Opera')<0) &&
93 (Element.getStyle(this.update, 'position')=='absolute')) {
96 (Element.getStyle(this.update, 'position')=='absolute')) {
94 new Insertion.After(this.update,
97 new Insertion.After(this.update,
95 '<iframe id="' + this.update.id + '_iefix" '+
98 '<iframe id="' + this.update.id + '_iefix" '+
96 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
99 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
97 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
100 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
98 this.iefix = $(this.update.id+'_iefix');
101 this.iefix = $(this.update.id+'_iefix');
99 }
102 }
100 if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
103 if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
101 },
104 },
102
105
103 fixIEOverlapping: function() {
106 fixIEOverlapping: function() {
104 Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
107 Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
105 this.iefix.style.zIndex = 1;
108 this.iefix.style.zIndex = 1;
106 this.update.style.zIndex = 2;
109 this.update.style.zIndex = 2;
107 Element.show(this.iefix);
110 Element.show(this.iefix);
108 },
111 },
109
112
110 hide: function() {
113 hide: function() {
111 this.stopIndicator();
114 this.stopIndicator();
112 if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
115 if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
113 if(this.iefix) Element.hide(this.iefix);
116 if(this.iefix) Element.hide(this.iefix);
114 },
117 },
115
118
116 startIndicator: function() {
119 startIndicator: function() {
117 if(this.options.indicator) Element.show(this.options.indicator);
120 if(this.options.indicator) Element.show(this.options.indicator);
118 },
121 },
119
122
120 stopIndicator: function() {
123 stopIndicator: function() {
121 if(this.options.indicator) Element.hide(this.options.indicator);
124 if(this.options.indicator) Element.hide(this.options.indicator);
122 },
125 },
123
126
124 onKeyPress: function(event) {
127 onKeyPress: function(event) {
125 if(this.active)
128 if(this.active)
126 switch(event.keyCode) {
129 switch(event.keyCode) {
127 case Event.KEY_TAB:
130 case Event.KEY_TAB:
128 case Event.KEY_RETURN:
131 case Event.KEY_RETURN:
129 this.selectEntry();
132 this.selectEntry();
130 Event.stop(event);
133 Event.stop(event);
131 case Event.KEY_ESC:
134 case Event.KEY_ESC:
132 this.hide();
135 this.hide();
133 this.active = false;
136 this.active = false;
134 Event.stop(event);
137 Event.stop(event);
135 return;
138 return;
136 case Event.KEY_LEFT:
139 case Event.KEY_LEFT:
137 case Event.KEY_RIGHT:
140 case Event.KEY_RIGHT:
138 return;
141 return;
139 case Event.KEY_UP:
142 case Event.KEY_UP:
140 this.markPrevious();
143 this.markPrevious();
141 this.render();
144 this.render();
142 if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
145 Event.stop(event);
143 return;
146 return;
144 case Event.KEY_DOWN:
147 case Event.KEY_DOWN:
145 this.markNext();
148 this.markNext();
146 this.render();
149 this.render();
147 if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
150 Event.stop(event);
148 return;
151 return;
149 }
152 }
150 else
153 else
151 if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
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 this.changed = true;
157 this.changed = true;
155 this.hasFocus = true;
158 this.hasFocus = true;
156
159
157 if(this.observer) clearTimeout(this.observer);
160 if(this.observer) clearTimeout(this.observer);
158 this.observer =
161 this.observer =
159 setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
162 setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
160 },
163 },
161
164
162 activate: function() {
165 activate: function() {
163 this.changed = false;
166 this.changed = false;
164 this.hasFocus = true;
167 this.hasFocus = true;
165 this.getUpdatedChoices();
168 this.getUpdatedChoices();
166 },
169 },
167
170
168 onHover: function(event) {
171 onHover: function(event) {
169 var element = Event.findElement(event, 'LI');
172 var element = Event.findElement(event, 'LI');
170 if(this.index != element.autocompleteIndex)
173 if(this.index != element.autocompleteIndex)
171 {
174 {
172 this.index = element.autocompleteIndex;
175 this.index = element.autocompleteIndex;
173 this.render();
176 this.render();
174 }
177 }
175 Event.stop(event);
178 Event.stop(event);
176 },
179 },
177
180
178 onClick: function(event) {
181 onClick: function(event) {
179 var element = Event.findElement(event, 'LI');
182 var element = Event.findElement(event, 'LI');
180 this.index = element.autocompleteIndex;
183 this.index = element.autocompleteIndex;
181 this.selectEntry();
184 this.selectEntry();
182 this.hide();
185 this.hide();
183 },
186 },
184
187
185 onBlur: function(event) {
188 onBlur: function(event) {
186 // needed to make click events working
189 // needed to make click events working
187 setTimeout(this.hide.bind(this), 250);
190 setTimeout(this.hide.bind(this), 250);
188 this.hasFocus = false;
191 this.hasFocus = false;
189 this.active = false;
192 this.active = false;
190 },
193 },
191
194
192 render: function() {
195 render: function() {
193 if(this.entryCount > 0) {
196 if(this.entryCount > 0) {
194 for (var i = 0; i < this.entryCount; i++)
197 for (var i = 0; i < this.entryCount; i++)
195 this.index==i ?
198 this.index==i ?
196 Element.addClassName(this.getEntry(i),"selected") :
199 Element.addClassName(this.getEntry(i),"selected") :
197 Element.removeClassName(this.getEntry(i),"selected");
200 Element.removeClassName(this.getEntry(i),"selected");
198
199 if(this.hasFocus) {
201 if(this.hasFocus) {
200 this.show();
202 this.show();
201 this.active = true;
203 this.active = true;
202 }
204 }
203 } else {
205 } else {
204 this.active = false;
206 this.active = false;
205 this.hide();
207 this.hide();
206 }
208 }
207 },
209 },
208
210
209 markPrevious: function() {
211 markPrevious: function() {
210 if(this.index > 0) this.index--
212 if(this.index > 0) this.index--
211 else this.index = this.entryCount-1;
213 else this.index = this.entryCount-1;
212 this.getEntry(this.index).scrollIntoView(true);
214 this.getEntry(this.index).scrollIntoView(true);
213 },
215 },
214
216
215 markNext: function() {
217 markNext: function() {
216 if(this.index < this.entryCount-1) this.index++
218 if(this.index < this.entryCount-1) this.index++
217 else this.index = 0;
219 else this.index = 0;
218 this.getEntry(this.index).scrollIntoView(false);
220 this.getEntry(this.index).scrollIntoView(false);
219 },
221 },
220
222
221 getEntry: function(index) {
223 getEntry: function(index) {
222 return this.update.firstChild.childNodes[index];
224 return this.update.firstChild.childNodes[index];
223 },
225 },
224
226
225 getCurrentEntry: function() {
227 getCurrentEntry: function() {
226 return this.getEntry(this.index);
228 return this.getEntry(this.index);
227 },
229 },
228
230
229 selectEntry: function() {
231 selectEntry: function() {
230 this.active = false;
232 this.active = false;
231 this.updateElement(this.getCurrentEntry());
233 this.updateElement(this.getCurrentEntry());
232 },
234 },
233
235
234 updateElement: function(selectedElement) {
236 updateElement: function(selectedElement) {
235 if (this.options.updateElement) {
237 if (this.options.updateElement) {
236 this.options.updateElement(selectedElement);
238 this.options.updateElement(selectedElement);
237 return;
239 return;
238 }
240 }
239 var value = '';
241 var value = '';
240 if (this.options.select) {
242 if (this.options.select) {
241 var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
243 var nodes = $(selectedElement).select('.' + this.options.select) || [];
242 if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
244 if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
243 } else
245 } else
244 value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
246 value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
245
247
246 var lastTokenPos = this.findLastToken();
248 var bounds = this.getTokenBounds();
247 if (lastTokenPos != -1) {
249 if (bounds[0] != -1) {
248 var newValue = this.element.value.substr(0, lastTokenPos + 1);
250 var newValue = this.element.value.substr(0, bounds[0]);
249 var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
251 var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
250 if (whitespace)
252 if (whitespace)
251 newValue += whitespace[0];
253 newValue += whitespace[0];
252 this.element.value = newValue + value;
254 this.element.value = newValue + value + this.element.value.substr(bounds[1]);
253 } else {
255 } else {
254 this.element.value = value;
256 this.element.value = value;
255 }
257 }
258 this.oldElementValue = this.element.value;
256 this.element.focus();
259 this.element.focus();
257
260
258 if (this.options.afterUpdateElement)
261 if (this.options.afterUpdateElement)
259 this.options.afterUpdateElement(this.element, selectedElement);
262 this.options.afterUpdateElement(this.element, selectedElement);
260 },
263 },
261
264
262 updateChoices: function(choices) {
265 updateChoices: function(choices) {
263 if(!this.changed && this.hasFocus) {
266 if(!this.changed && this.hasFocus) {
264 this.update.innerHTML = choices;
267 this.update.innerHTML = choices;
265 Element.cleanWhitespace(this.update);
268 Element.cleanWhitespace(this.update);
266 Element.cleanWhitespace(this.update.down());
269 Element.cleanWhitespace(this.update.down());
267
270
268 if(this.update.firstChild && this.update.down().childNodes) {
271 if(this.update.firstChild && this.update.down().childNodes) {
269 this.entryCount =
272 this.entryCount =
270 this.update.down().childNodes.length;
273 this.update.down().childNodes.length;
271 for (var i = 0; i < this.entryCount; i++) {
274 for (var i = 0; i < this.entryCount; i++) {
272 var entry = this.getEntry(i);
275 var entry = this.getEntry(i);
273 entry.autocompleteIndex = i;
276 entry.autocompleteIndex = i;
274 this.addObservers(entry);
277 this.addObservers(entry);
275 }
278 }
276 } else {
279 } else {
277 this.entryCount = 0;
280 this.entryCount = 0;
278 }
281 }
279
282
280 this.stopIndicator();
283 this.stopIndicator();
281 this.index = 0;
284 this.index = 0;
282
285
283 if(this.entryCount==1 && this.options.autoSelect) {
286 if(this.entryCount==1 && this.options.autoSelect) {
284 this.selectEntry();
287 this.selectEntry();
285 this.hide();
288 this.hide();
286 } else {
289 } else {
287 this.render();
290 this.render();
288 }
291 }
289 }
292 }
290 },
293 },
291
294
292 addObservers: function(element) {
295 addObservers: function(element) {
293 Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
296 Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
294 Event.observe(element, "click", this.onClick.bindAsEventListener(this));
297 Event.observe(element, "click", this.onClick.bindAsEventListener(this));
295 },
298 },
296
299
297 onObserverEvent: function() {
300 onObserverEvent: function() {
298 this.changed = false;
301 this.changed = false;
302 this.tokenBounds = null;
299 if(this.getToken().length>=this.options.minChars) {
303 if(this.getToken().length>=this.options.minChars) {
300 this.startIndicator();
301 this.getUpdatedChoices();
304 this.getUpdatedChoices();
302 } else {
305 } else {
303 this.active = false;
306 this.active = false;
304 this.hide();
307 this.hide();
305 }
308 }
309 this.oldElementValue = this.element.value;
306 },
310 },
307
311
308 getToken: function() {
312 getToken: function() {
309 var tokenPos = this.findLastToken();
313 var bounds = this.getTokenBounds();
310 if (tokenPos != -1)
314 return this.element.value.substring(bounds[0], bounds[1]).strip();
311 var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
315 },
312 else
316
313 var ret = this.element.value;
317 getTokenBounds: function() {
314
318 if (null != this.tokenBounds) return this.tokenBounds;
315 return /\n/.test(ret) ? '' : ret;
319 var value = this.element.value;
316 },
320 if (value.strip().empty()) return [-1, 0];
317
321 var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
318 findLastToken: function() {
322 var offset = (diff == this.oldElementValue.length ? 1 : 0);
319 var lastTokenPos = -1;
323 var prevTokenPos = -1, nextTokenPos = value.length;
320
324 var tp;
321 for (var i=0; i<this.options.tokens.length; i++) {
325 for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
322 var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
326 tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
323 if (thisTokenPos > lastTokenPos)
327 if (tp > prevTokenPos) prevTokenPos = tp;
324 lastTokenPos = thisTokenPos;
328 tp = value.indexOf(this.options.tokens[index], diff + offset);
329 if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
325 }
330 }
326 return lastTokenPos;
331 return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
327 }
332 }
328 }
333 });
334
335 Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
336 var boundary = Math.min(newS.length, oldS.length);
337 for (var index = 0; index < boundary; ++index)
338 if (newS[index] != oldS[index])
339 return index;
340 return boundary;
341 };
329
342
330 Ajax.Autocompleter = Class.create();
343 Ajax.Autocompleter = Class.create(Autocompleter.Base, {
331 Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
332 initialize: function(element, update, url, options) {
344 initialize: function(element, update, url, options) {
333 this.baseInitialize(element, update, options);
345 this.baseInitialize(element, update, options);
334 this.options.asynchronous = true;
346 this.options.asynchronous = true;
335 this.options.onComplete = this.onComplete.bind(this);
347 this.options.onComplete = this.onComplete.bind(this);
336 this.options.defaultParams = this.options.parameters || null;
348 this.options.defaultParams = this.options.parameters || null;
337 this.url = url;
349 this.url = url;
338 },
350 },
339
351
340 getUpdatedChoices: function() {
352 getUpdatedChoices: function() {
341 entry = encodeURIComponent(this.options.paramName) + '=' +
353 this.startIndicator();
354
355 var entry = encodeURIComponent(this.options.paramName) + '=' +
342 encodeURIComponent(this.getToken());
356 encodeURIComponent(this.getToken());
343
357
344 this.options.parameters = this.options.callback ?
358 this.options.parameters = this.options.callback ?
345 this.options.callback(this.element, entry) : entry;
359 this.options.callback(this.element, entry) : entry;
346
360
347 if(this.options.defaultParams)
361 if(this.options.defaultParams)
348 this.options.parameters += '&' + this.options.defaultParams;
362 this.options.parameters += '&' + this.options.defaultParams;
349
363
350 new Ajax.Request(this.url, this.options);
364 new Ajax.Request(this.url, this.options);
351 },
365 },
352
366
353 onComplete: function(request) {
367 onComplete: function(request) {
354 this.updateChoices(request.responseText);
368 this.updateChoices(request.responseText);
355 }
369 }
356
357 });
370 });
358
371
359 // The local array autocompleter. Used when you'd prefer to
372 // The local array autocompleter. Used when you'd prefer to
360 // inject an array of autocompletion options into the page, rather
373 // inject an array of autocompletion options into the page, rather
361 // than sending out Ajax queries, which can be quite slow sometimes.
374 // than sending out Ajax queries, which can be quite slow sometimes.
362 //
375 //
363 // The constructor takes four parameters. The first two are, as usual,
376 // The constructor takes four parameters. The first two are, as usual,
364 // the id of the monitored textbox, and id of the autocompletion menu.
377 // the id of the monitored textbox, and id of the autocompletion menu.
365 // The third is the array you want to autocomplete from, and the fourth
378 // The third is the array you want to autocomplete from, and the fourth
366 // is the options block.
379 // is the options block.
367 //
380 //
368 // Extra local autocompletion options:
381 // Extra local autocompletion options:
369 // - choices - How many autocompletion choices to offer
382 // - choices - How many autocompletion choices to offer
370 //
383 //
371 // - partialSearch - If false, the autocompleter will match entered
384 // - partialSearch - If false, the autocompleter will match entered
372 // text only at the beginning of strings in the
385 // text only at the beginning of strings in the
373 // autocomplete array. Defaults to true, which will
386 // autocomplete array. Defaults to true, which will
374 // match text at the beginning of any *word* in the
387 // match text at the beginning of any *word* in the
375 // strings in the autocomplete array. If you want to
388 // strings in the autocomplete array. If you want to
376 // search anywhere in the string, additionally set
389 // search anywhere in the string, additionally set
377 // the option fullSearch to true (default: off).
390 // the option fullSearch to true (default: off).
378 //
391 //
379 // - fullSsearch - Search anywhere in autocomplete array strings.
392 // - fullSsearch - Search anywhere in autocomplete array strings.
380 //
393 //
381 // - partialChars - How many characters to enter before triggering
394 // - partialChars - How many characters to enter before triggering
382 // a partial match (unlike minChars, which defines
395 // a partial match (unlike minChars, which defines
383 // how many characters are required to do any match
396 // how many characters are required to do any match
384 // at all). Defaults to 2.
397 // at all). Defaults to 2.
385 //
398 //
386 // - ignoreCase - Whether to ignore case when autocompleting.
399 // - ignoreCase - Whether to ignore case when autocompleting.
387 // Defaults to true.
400 // Defaults to true.
388 //
401 //
389 // It's possible to pass in a custom function as the 'selector'
402 // It's possible to pass in a custom function as the 'selector'
390 // option, if you prefer to write your own autocompletion logic.
403 // option, if you prefer to write your own autocompletion logic.
391 // In that case, the other options above will not apply unless
404 // In that case, the other options above will not apply unless
392 // you support them.
405 // you support them.
393
406
394 Autocompleter.Local = Class.create();
407 Autocompleter.Local = Class.create(Autocompleter.Base, {
395 Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
396 initialize: function(element, update, array, options) {
408 initialize: function(element, update, array, options) {
397 this.baseInitialize(element, update, options);
409 this.baseInitialize(element, update, options);
398 this.options.array = array;
410 this.options.array = array;
399 },
411 },
400
412
401 getUpdatedChoices: function() {
413 getUpdatedChoices: function() {
402 this.updateChoices(this.options.selector(this));
414 this.updateChoices(this.options.selector(this));
403 },
415 },
404
416
405 setOptions: function(options) {
417 setOptions: function(options) {
406 this.options = Object.extend({
418 this.options = Object.extend({
407 choices: 10,
419 choices: 10,
408 partialSearch: true,
420 partialSearch: true,
409 partialChars: 2,
421 partialChars: 2,
410 ignoreCase: true,
422 ignoreCase: true,
411 fullSearch: false,
423 fullSearch: false,
412 selector: function(instance) {
424 selector: function(instance) {
413 var ret = []; // Beginning matches
425 var ret = []; // Beginning matches
414 var partial = []; // Inside matches
426 var partial = []; // Inside matches
415 var entry = instance.getToken();
427 var entry = instance.getToken();
416 var count = 0;
428 var count = 0;
417
429
418 for (var i = 0; i < instance.options.array.length &&
430 for (var i = 0; i < instance.options.array.length &&
419 ret.length < instance.options.choices ; i++) {
431 ret.length < instance.options.choices ; i++) {
420
432
421 var elem = instance.options.array[i];
433 var elem = instance.options.array[i];
422 var foundPos = instance.options.ignoreCase ?
434 var foundPos = instance.options.ignoreCase ?
423 elem.toLowerCase().indexOf(entry.toLowerCase()) :
435 elem.toLowerCase().indexOf(entry.toLowerCase()) :
424 elem.indexOf(entry);
436 elem.indexOf(entry);
425
437
426 while (foundPos != -1) {
438 while (foundPos != -1) {
427 if (foundPos == 0 && elem.length != entry.length) {
439 if (foundPos == 0 && elem.length != entry.length) {
428 ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
440 ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
429 elem.substr(entry.length) + "</li>");
441 elem.substr(entry.length) + "</li>");
430 break;
442 break;
431 } else if (entry.length >= instance.options.partialChars &&
443 } else if (entry.length >= instance.options.partialChars &&
432 instance.options.partialSearch && foundPos != -1) {
444 instance.options.partialSearch && foundPos != -1) {
433 if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
445 if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
434 partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
446 partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
435 elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
447 elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
436 foundPos + entry.length) + "</li>");
448 foundPos + entry.length) + "</li>");
437 break;
449 break;
438 }
450 }
439 }
451 }
440
452
441 foundPos = instance.options.ignoreCase ?
453 foundPos = instance.options.ignoreCase ?
442 elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
454 elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
443 elem.indexOf(entry, foundPos + 1);
455 elem.indexOf(entry, foundPos + 1);
444
456
445 }
457 }
446 }
458 }
447 if (partial.length)
459 if (partial.length)
448 ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
460 ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
449 return "<ul>" + ret.join('') + "</ul>";
461 return "<ul>" + ret.join('') + "</ul>";
450 }
462 }
451 }, options || {});
463 }, options || { });
452 }
464 }
453 });
465 });
454
466
455 // AJAX in-place editor
467 // AJAX in-place editor and collection editor
456 //
468 // Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
457 // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
458
469
459 // Use this if you notice weird scrolling problems on some browsers,
470 // Use this if you notice weird scrolling problems on some browsers,
460 // the DOM might be a bit confused when this gets called so do this
471 // the DOM might be a bit confused when this gets called so do this
461 // waits 1 ms (with setTimeout) until it does the activation
472 // waits 1 ms (with setTimeout) until it does the activation
462 Field.scrollFreeActivate = function(field) {
473 Field.scrollFreeActivate = function(field) {
463 setTimeout(function() {
474 setTimeout(function() {
464 Field.activate(field);
475 Field.activate(field);
465 }, 1);
476 }, 1);
466 }
477 }
467
478
468 Ajax.InPlaceEditor = Class.create();
479 Ajax.InPlaceEditor = Class.create({
469 Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
470 Ajax.InPlaceEditor.prototype = {
471 initialize: function(element, url, options) {
480 initialize: function(element, url, options) {
472 this.url = url;
481 this.url = url;
473 this.element = $(element);
482 this.element = element = $(element);
474
483 this.prepareOptions();
475 this.options = Object.extend({
484 this._controls = { };
476 paramName: "value",
485 arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
477 okButton: true,
486 Object.extend(this.options, options || { });
478 okText: "ok",
487 if (!this.options.formId && this.element.id) {
479 cancelLink: true,
488 this.options.formId = this.element.id + '-inplaceeditor';
480 cancelText: "cancel",
489 if ($(this.options.formId))
481 savingText: "Saving...",
490 this.options.formId = '';
482 clickToEditText: "Click to edit",
483 okText: "ok",
484 rows: 1,
485 onComplete: function(transport, element) {
486 new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
487 },
488 onFailure: function(transport) {
489 alert("Error communicating with the server: " + transport.responseText.stripTags());
490 },
491 callback: function(form) {
492 return Form.serialize(form);
493 },
494 handleLineBreaks: true,
495 loadingText: 'Loading...',
496 savingClassName: 'inplaceeditor-saving',
497 loadingClassName: 'inplaceeditor-loading',
498 formClassName: 'inplaceeditor-form',
499 highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
500 highlightendcolor: "#FFFFFF",
501 externalControl: null,
502 submitOnBlur: false,
503 ajaxOptions: {},
504 evalScripts: false
505 }, options || {});
506
507 if(!this.options.formId && this.element.id) {
508 this.options.formId = this.element.id + "-inplaceeditor";
509 if ($(this.options.formId)) {
510 // there's already a form with that name, don't specify an id
511 this.options.formId = null;
512 }
513 }
491 }
514
492 if (this.options.externalControl)
515 if (this.options.externalControl) {
516 this.options.externalControl = $(this.options.externalControl);
493 this.options.externalControl = $(this.options.externalControl);
517 }
494 if (!this.options.externalControl)
518
495 this.options.externalControlOnly = false;
519 this.originalBackground = Element.getStyle(this.element, 'background-color');
496 this._originalBackground = this.element.getStyle('background-color') || 'transparent';
520 if (!this.originalBackground) {
521 this.originalBackground = "transparent";
522 }
523
524 this.element.title = this.options.clickToEditText;
497 this.element.title = this.options.clickToEditText;
525
498 this._boundCancelHandler = this.handleFormCancellation.bind(this);
526 this.onclickListener = this.enterEditMode.bindAsEventListener(this);
499 this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
527 this.mouseoverListener = this.enterHover.bindAsEventListener(this);
500 this._boundFailureHandler = this.handleAJAXFailure.bind(this);
528 this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
501 this._boundSubmitHandler = this.handleFormSubmission.bind(this);
529 Event.observe(this.element, 'click', this.onclickListener);
502 this._boundWrapperHandler = this.wrapUp.bind(this);
530 Event.observe(this.element, 'mouseover', this.mouseoverListener);
503 this.registerListeners();
531 Event.observe(this.element, 'mouseout', this.mouseoutListener);
504 },
532 if (this.options.externalControl) {
505 checkForEscapeOrReturn: function(e) {
533 Event.observe(this.options.externalControl, 'click', this.onclickListener);
506 if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
534 Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
507 if (Event.KEY_ESC == e.keyCode)
535 Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
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);
545 }
546 Element.hide(this.element);
547 this.createForm();
548 this.element.parentNode.insertBefore(this.form, this.element);
549 if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
550 // stop the event to avoid a page refresh in Safari
551 if (evt) {
552 Event.stop(evt);
553 }
554 return false;
555 },
556 createForm: function() {
557 this.form = document.createElement("form");
558 this.form.id = this.options.formId;
559 Element.addClassName(this.form, this.options.formClassName)
560 this.form.onsubmit = this.onSubmit.bind(this);
561
562 this.createEditField();
563
564 if (this.options.textarea) {
565 var br = document.createElement("br");
566 this.form.appendChild(br);
567 }
568
569 if (this.options.okButton) {
570 okButton = document.createElement("input");
571 okButton.type = "submit";
572 okButton.value = this.options.okText;
573 okButton.className = 'editor_ok_button';
574 this.form.appendChild(okButton);
575 }
576
577 if (this.options.cancelLink) {
578 cancelLink = document.createElement("a");
579 cancelLink.href = "#";
580 cancelLink.appendChild(document.createTextNode(this.options.cancelText));
581 cancelLink.onclick = this.onclickCancel.bind(this);
582 cancelLink.className = 'editor_cancel';
583 this.form.appendChild(cancelLink);
584 }
585 },
586 hasHTMLLineBreaks: function(string) {
587 if (!this.options.handleLineBreaks) return false;
588 return string.match(/<br/i) || string.match(/<p>/i);
589 },
590 convertHTMLLineBreaks: function(string) {
591 return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
592 },
593 createEditField: function() {
536 createEditField: function() {
594 var text;
537 var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
595 if(this.options.loadTextURL) {
538 var fld;
596 text = this.options.loadingText;
539 if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
597 } else {
540 fld = document.createElement('input');
598 text = this.getText();
541 fld.type = 'text';
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;
542 var size = this.options.size || this.options.cols || 0;
613 if (size != 0) textField.size = size;
543 if (0 < size) fld.size = size;
614 if (this.options.submitOnBlur)
615 textField.onblur = this.onSubmit.bind(this);
616 this.editField = textField;
617 } else {
544 } else {
618 this.options.textarea = true;
545 fld = document.createElement('textarea');
619 var textArea = document.createElement("textarea");
546 fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
620 textArea.obj = this;
547 fld.cols = this.options.cols || 40;
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 }
548 }
630
549 fld.name = this.options.paramName;
631 if(this.options.loadTextURL) {
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)
632 this.loadExternalText();
556 this.loadExternalText();
633 }
557 this._form.appendChild(this._controls.editor);
634 this.form.appendChild(this.editField);
558 },
559 createForm: function() {
560 var ipe = this;
561 function addText(mode, condition) {
562 var text = ipe.options['text' + mode + 'Controls'];
563 if (!text || condition === false) return;
564 ipe._form.appendChild(document.createTextNode(text));
565 };
566 this._form = $(document.createElement('form'));
567 this._form.id = this.options.formId;
568 this._form.addClassName(this.options.formClassName);
569 this._form.onsubmit = this._boundSubmitHandler;
570 this.createEditField();
571 if ('textarea' == this._controls.editor.tagName.toLowerCase())
572 this._form.appendChild(document.createElement('br'));
573 if (this.options.onFormCustomization)
574 this.options.onFormCustomization(this, this._form);
575 addText('Before', this.options.okControl || this.options.cancelControl);
576 this.createControl('ok', this._boundSubmitHandler);
577 addText('Between', this.options.okControl && this.options.cancelControl);
578 this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
579 addText('After', this.options.okControl || this.options.cancelControl);
580 },
581 destroy: function() {
582 if (this._oldInnerHTML)
583 this.element.innerHTML = this._oldInnerHTML;
584 this.leaveEditMode();
585 this.unregisterListeners();
586 },
587 enterEditMode: function(e) {
588 if (this._saving || this._editing) return;
589 this._editing = true;
590 this.triggerCallback('onEnterEditMode');
591 if (this.options.externalControl)
592 this.options.externalControl.hide();
593 this.element.hide();
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 getText: function() {
606 getText: function() {
637 return this.element.innerHTML;
607 return this.element.innerHTML;
638 },
608 },
639 loadExternalText: function() {
609 handleAJAXFailure: function(transport) {
640 Element.addClassName(this.form, this.options.loadingClassName);
610 this.triggerCallback('onFailure', transport);
641 this.editField.disabled = true;
611 if (this._oldInnerHTML) {
642 new Ajax.Request(
612 this.element.innerHTML = this._oldInnerHTML;
643 this.options.loadTextURL,
613 this._oldInnerHTML = null;
644 Object.extend({
645 asynchronous: true,
646 onComplete: this.onLoadedExternalText.bind(this)
647 }, this.options.ajaxOptions)
648 );
649 },
650 onLoadedExternalText: function(transport) {
651 Element.removeClassName(this.form, this.options.loadingClassName);
652 this.editField.disabled = false;
653 this.editField.value = transport.responseText.stripTags();
654 Field.scrollFreeActivate(this.editField);
655 },
656 onclickCancel: function() {
657 this.onComplete();
658 this.leaveEditMode();
659 return false;
660 },
661 onFailure: function(transport) {
662 this.options.onFailure(transport);
663 if (this.oldInnerHTML) {
664 this.element.innerHTML = this.oldInnerHTML;
665 this.oldInnerHTML = null;
666 }
614 }
667 return false;
668 },
615 },
669 onSubmit: function() {
616 handleFormCancellation: function(e) {
670 // onLoading resets these so we need to save them away for the Ajax call
617 this.wrapUp();
671 var form = this.form;
618 if (e) Event.stop(e);
672 var value = this.editField.value;
619 },
673
620 handleFormSubmission: function(e) {
674 // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
621 var form = this._form;
675 // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
622 var value = $F(this._controls.editor);
676 // to be displayed indefinitely
623 this.prepareSubmission();
677 this.onLoading();
624 var params = this.options.callback(form, value) || '';
678
625 if (Object.isString(params))
679 if (this.options.evalScripts) {
626 params = params.toQueryParams();
680 new Ajax.Request(
627 params.editorId = this.element.id;
681 this.url, Object.extend({
628 if (this.options.htmlResponse) {
682 parameters: this.options.callback(form, value),
629 var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
683 onComplete: this.onComplete.bind(this),
630 Object.extend(options, {
684 onFailure: this.onFailure.bind(this),
631 parameters: params,
685 asynchronous:true,
632 onComplete: this._boundWrapperHandler,
686 evalScripts:true
633 onFailure: this._boundFailureHandler
687 }, this.options.ajaxOptions));
634 });
688 } else {
635 new Ajax.Updater({ success: this.element }, this.url, options);
689 new Ajax.Updater(
636 } else {
690 { success: this.element,
637 var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
691 // don't update on failure (this could be an option)
638 Object.extend(options, {
692 failure: null },
639 parameters: params,
693 this.url, Object.extend({
640 onComplete: this._boundWrapperHandler,
694 parameters: this.options.callback(form, value),
641 onFailure: this._boundFailureHandler
695 onComplete: this.onComplete.bind(this),
642 });
696 onFailure: this.onFailure.bind(this)
643 new Ajax.Request(this.url, options);
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 }
644 }
703 return false;
645 if (e) Event.stop(e);
646 },
647 leaveEditMode: function() {
648 this.element.removeClassName(this.options.savingClassName);
649 this.removeForm();
650 this.leaveHover();
651 this.element.style.backgroundColor = this._originalBackground;
652 this.element.show();
653 if (this.options.externalControl)
654 this.options.externalControl.show();
655 this._saving = false;
656 this._editing = false;
657 this._oldInnerHTML = null;
658 this.triggerCallback('onLeaveEditMode');
659 },
660 leaveHover: function(e) {
661 if (this.options.hoverClassName)
662 this.element.removeClassName(this.options.hoverClassName);
663 if (this._saving) return;
664 this.triggerCallback('onLeaveHover');
704 },
665 },
705 onLoading: function() {
666 loadExternalText: function() {
706 this.saving = true;
667 this._form.addClassName(this.options.loadingClassName);
668 this._controls.editor.disabled = true;
669 var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
670 Object.extend(options, {
671 parameters: 'editorId=' + encodeURIComponent(this.element.id),
672 onComplete: Prototype.emptyFunction,
673 onSuccess: function(transport) {
674 this._form.removeClassName(this.options.loadingClassName);
675 var text = transport.responseText;
676 if (this.options.stripLoadedTextTags)
677 text = text.stripTags();
678 this._controls.editor.value = text;
679 this._controls.editor.disabled = false;
680 this.postProcessEditField();
681 }.bind(this),
682 onFailure: this._boundFailureHandler
683 });
684 new Ajax.Request(this.options.loadTextURL, options);
685 },
686 postProcessEditField: function() {
687 var fpc = this.options.fieldPostCreation;
688 if (fpc)
689 $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
690 },
691 prepareOptions: function() {
692 this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
693 Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
694 [this._extraDefaultOptions].flatten().compact().each(function(defs) {
695 Object.extend(this.options, defs);
696 }.bind(this));
697 },
698 prepareSubmission: function() {
699 this._saving = true;
707 this.removeForm();
700 this.removeForm();
708 this.leaveHover();
701 this.leaveHover();
709 this.showSaving();
702 this.showSaving();
710 },
703 },
704 registerListeners: function() {
705 this._listeners = { };
706 var listener;
707 $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
708 listener = this[pair.value].bind(this);
709 this._listeners[pair.key] = listener;
710 if (!this.options.externalControlOnly)
711 this.element.observe(pair.key, listener);
712 if (this.options.externalControl)
713 this.options.externalControl.observe(pair.key, listener);
714 }.bind(this));
715 },
716 removeForm: function() {
717 if (!this._form) return;
718 this._form.remove();
719 this._form = null;
720 this._controls = { };
721 },
711 showSaving: function() {
722 showSaving: function() {
712 this.oldInnerHTML = this.element.innerHTML;
723 this._oldInnerHTML = this.element.innerHTML;
713 this.element.innerHTML = this.options.savingText;
724 this.element.innerHTML = this.options.savingText;
714 Element.addClassName(this.element, this.options.savingClassName);
725 this.element.addClassName(this.options.savingClassName);
715 this.element.style.backgroundColor = this.originalBackground;
726 this.element.style.backgroundColor = this._originalBackground;
716 Element.show(this.element);
727 this.element.show();
717 },
728 },
718 removeForm: function() {
729 triggerCallback: function(cbName, arg) {
719 if(this.form) {
730 if ('function' == typeof this.options[cbName]) {
720 if (this.form.parentNode) Element.remove(this.form);
731 this.options[cbName](this, arg);
721 this.form = null;
722 }
732 }
723 },
733 },
724 enterHover: function() {
734 unregisterListeners: function() {
725 if (this.saving) return;
735 $H(this._listeners).each(function(pair) {
726 this.element.style.backgroundColor = this.options.highlightcolor;
736 if (!this.options.externalControlOnly)
727 if (this.effect) {
737 this.element.stopObserving(pair.key, pair.value);
728 this.effect.cancel();
738 if (this.options.externalControl)
729 }
739 this.options.externalControl.stopObserving(pair.key, pair.value);
730 Element.addClassName(this.element, this.options.hoverClassName)
740 }.bind(this));
731 },
741 },
732 leaveHover: function() {
742 wrapUp: function(transport) {
733 if (this.options.backgroundColor) {
743 this.leaveEditMode();
734 this.element.style.backgroundColor = this.oldBackground;
744 // Can't use triggerCallback due to backward compatibility: requires
735 }
745 // binding + direct element
736 Element.removeClassName(this.element, this.options.hoverClassName)
746 this._boundComplete(transport, this.element);
737 if (this.saving) return;
747 }
738 this.effect = new Effect.Highlight(this.element, {
748 });
739 startcolor: this.options.highlightcolor,
749
740 endcolor: this.options.highlightendcolor,
750 Object.extend(Ajax.InPlaceEditor.prototype, {
741 restorecolor: this.originalBackground
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);
758 },
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() {
791
745 Element.removeClassName(this.element, this.options.savingClassName);
792 showLoadingText: function(text) {
746 this.removeForm();
793 this._controls.editor.disabled = true;
747 this.leaveHover();
794 var tempOption = this._controls.editor.firstChild;
748 this.element.style.backgroundColor = this.originalBackground;
795 if (!tempOption) {
749 Element.show(this.element);
796 tempOption = document.createElement('option');
750 if (this.options.externalControl) {
797 tempOption.value = '';
751 Element.show(this.options.externalControl);
798 this._controls.editor.appendChild(tempOption);
799 tempOption.selected = true;
752 }
800 }
753 this.editing = false;
801 tempOption.update((text || '').stripScripts().stripTags());
754 this.saving = false;
755 this.oldInnerHTML = null;
756 this.onLeaveEditMode();
757 },
802 },
758 onComplete: function(transport) {
803
759 this.leaveEditMode();
804 checkForExternalText: function() {
760 this.options.onComplete.bind(this)(transport, this.element);
805 this._text = this.getText();
806 if (this.options.loadTextURL)
807 this.loadExternalText();
808 else
809 this.buildOptionList();
761 },
810 },
762 onEnterEditMode: function() {},
811
763 onLeaveEditMode: function() {},
812 loadExternalText: function() {
764 dispose: function() {
813 this.showLoadingText(this.options.loadingText);
765 if (this.oldInnerHTML) {
814 var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
766 this.element.innerHTML = this.oldInnerHTML;
815 Object.extend(options, {
767 }
816 parameters: 'editorId=' + encodeURIComponent(this.element.id),
768 this.leaveEditMode();
817 onComplete: Prototype.emptyFunction,
769 Event.stopObserving(this.element, 'click', this.onclickListener);
818 onSuccess: function(transport) {
770 Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
819 this._text = transport.responseText.strip();
771 Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
820 this.buildOptionList();
772 if (this.options.externalControl) {
821 }.bind(this),
773 Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
822 onFailure: this.onFailure
774 Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
823 });
775 Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
824 new Ajax.Request(this.options.loadTextURL, options);
776 }
825 },
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 }
778 };
848 });
779
849
780 Ajax.InPlaceCollectionEditor = Class.create();
850 //**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
781 Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
851 //**** This only exists for a while, in order to let ****
782 Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
852 //**** users adapt to the new API. Read up on the new ****
783 createEditField: function() {
853 //**** API and convert your code to it ASAP! ****
784 if (!this.cached_selectTag) {
854
785 var selectTag = document.createElement("select");
855 Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
786 var collection = this.options.collection || [];
856 if (!options) return;
787 var optionTag;
857 function fallback(name, expr) {
788 collection.each(function(e,i) {
858 if (name in options || expr === undefined) return;
789 optionTag = document.createElement("option");
859 options[name] = expr;
790 optionTag.value = (e instanceof Array) ? e[0] : e;
860 };
791 if((typeof this.options.value == 'undefined') &&
861 fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
792 ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
862 options.cancelLink == options.cancelButton == false ? false : undefined)));
793 if(this.options.value==optionTag.value) optionTag.selected = true;
863 fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
794 optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
864 options.okLink == options.okButton == false ? false : undefined)));
795 selectTag.appendChild(optionTag);
865 fallback('highlightColor', options.highlightcolor);
796 }.bind(this));
866 fallback('highlightEndColor', options.highlightendcolor);
797 this.cached_selectTag = selectTag;
867 };
798 }
799
868
800 this.editField = this.cached_selectTag;
869 Object.extend(Ajax.InPlaceEditor, {
801 if(this.options.loadTextURL) this.loadExternalText();
870 DefaultOptions: {
802 this.form.appendChild(this.editField);
871 ajaxOptions: { },
803 this.options.callback = function(form, value) {
872 autoRows: 3, // Use when multi-line w/ rows == 1
804 return "value=" + encodeURIComponent(value);
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 });
805 }
926 }
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 // Delayed observer, like Form.Element.Observer,
940 // Delayed observer, like Form.Element.Observer,
810 // but waits for delay after last key input
941 // but waits for delay after last key input
811 // Ideal for live-search fields
942 // Ideal for live-search fields
812
943
813 Form.Element.DelayedObserver = Class.create();
944 Form.Element.DelayedObserver = Class.create({
814 Form.Element.DelayedObserver.prototype = {
815 initialize: function(element, delay, callback) {
945 initialize: function(element, delay, callback) {
816 this.delay = delay || 0.5;
946 this.delay = delay || 0.5;
817 this.element = $(element);
947 this.element = $(element);
818 this.callback = callback;
948 this.callback = callback;
819 this.timer = null;
949 this.timer = null;
820 this.lastValue = $F(this.element);
950 this.lastValue = $F(this.element);
821 Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
951 Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
822 },
952 },
823 delayedListener: function(event) {
953 delayedListener: function(event) {
824 if(this.lastValue == $F(this.element)) return;
954 if(this.lastValue == $F(this.element)) return;
825 if(this.timer) clearTimeout(this.timer);
955 if(this.timer) clearTimeout(this.timer);
826 this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
956 this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
827 this.lastValue = $F(this.element);
957 this.lastValue = $F(this.element);
828 },
958 },
829 onTimerEvent: function() {
959 onTimerEvent: function() {
830 this.timer = null;
960 this.timer = null;
831 this.callback(this.element, $F(this.element));
961 this.callback(this.element, $F(this.element));
832 }
962 }
833 };
963 });
@@ -1,942 +1,972
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 // (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
2 // (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
3 //
3 //
4 // script.aculo.us is freely distributable under the terms of an MIT-style license.
4 // script.aculo.us is freely distributable under the terms of an MIT-style license.
5 // For details, see the script.aculo.us web site: http://script.aculo.us/
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 throw("dragdrop.js requires including script.aculo.us' effects.js library");
8 throw("dragdrop.js requires including script.aculo.us' effects.js library");
9
9
10 var Droppables = {
10 var Droppables = {
11 drops: [],
11 drops: [],
12
12
13 remove: function(element) {
13 remove: function(element) {
14 this.drops = this.drops.reject(function(d) { return d.element==$(element) });
14 this.drops = this.drops.reject(function(d) { return d.element==$(element) });
15 },
15 },
16
16
17 add: function(element) {
17 add: function(element) {
18 element = $(element);
18 element = $(element);
19 var options = Object.extend({
19 var options = Object.extend({
20 greedy: true,
20 greedy: true,
21 hoverclass: null,
21 hoverclass: null,
22 tree: false
22 tree: false
23 }, arguments[1] || {});
23 }, arguments[1] || { });
24
24
25 // cache containers
25 // cache containers
26 if(options.containment) {
26 if(options.containment) {
27 options._containers = [];
27 options._containers = [];
28 var containment = options.containment;
28 var containment = options.containment;
29 if((typeof containment == 'object') &&
29 if(Object.isArray(containment)) {
30 (containment.constructor == Array)) {
31 containment.each( function(c) { options._containers.push($(c)) });
30 containment.each( function(c) { options._containers.push($(c)) });
32 } else {
31 } else {
33 options._containers.push($(containment));
32 options._containers.push($(containment));
34 }
33 }
35 }
34 }
36
35
37 if(options.accept) options.accept = [options.accept].flatten();
36 if(options.accept) options.accept = [options.accept].flatten();
38
37
39 Element.makePositioned(element); // fix IE
38 Element.makePositioned(element); // fix IE
40 options.element = element;
39 options.element = element;
41
40
42 this.drops.push(options);
41 this.drops.push(options);
43 },
42 },
44
43
45 findDeepestChild: function(drops) {
44 findDeepestChild: function(drops) {
46 deepest = drops[0];
45 deepest = drops[0];
47
46
48 for (i = 1; i < drops.length; ++i)
47 for (i = 1; i < drops.length; ++i)
49 if (Element.isParent(drops[i].element, deepest.element))
48 if (Element.isParent(drops[i].element, deepest.element))
50 deepest = drops[i];
49 deepest = drops[i];
51
50
52 return deepest;
51 return deepest;
53 },
52 },
54
53
55 isContained: function(element, drop) {
54 isContained: function(element, drop) {
56 var containmentNode;
55 var containmentNode;
57 if(drop.tree) {
56 if(drop.tree) {
58 containmentNode = element.treeNode;
57 containmentNode = element.treeNode;
59 } else {
58 } else {
60 containmentNode = element.parentNode;
59 containmentNode = element.parentNode;
61 }
60 }
62 return drop._containers.detect(function(c) { return containmentNode == c });
61 return drop._containers.detect(function(c) { return containmentNode == c });
63 },
62 },
64
63
65 isAffected: function(point, element, drop) {
64 isAffected: function(point, element, drop) {
66 return (
65 return (
67 (drop.element!=element) &&
66 (drop.element!=element) &&
68 ((!drop._containers) ||
67 ((!drop._containers) ||
69 this.isContained(element, drop)) &&
68 this.isContained(element, drop)) &&
70 ((!drop.accept) ||
69 ((!drop.accept) ||
71 (Element.classNames(element).detect(
70 (Element.classNames(element).detect(
72 function(v) { return drop.accept.include(v) } ) )) &&
71 function(v) { return drop.accept.include(v) } ) )) &&
73 Position.within(drop.element, point[0], point[1]) );
72 Position.within(drop.element, point[0], point[1]) );
74 },
73 },
75
74
76 deactivate: function(drop) {
75 deactivate: function(drop) {
77 if(drop.hoverclass)
76 if(drop.hoverclass)
78 Element.removeClassName(drop.element, drop.hoverclass);
77 Element.removeClassName(drop.element, drop.hoverclass);
79 this.last_active = null;
78 this.last_active = null;
80 },
79 },
81
80
82 activate: function(drop) {
81 activate: function(drop) {
83 if(drop.hoverclass)
82 if(drop.hoverclass)
84 Element.addClassName(drop.element, drop.hoverclass);
83 Element.addClassName(drop.element, drop.hoverclass);
85 this.last_active = drop;
84 this.last_active = drop;
86 },
85 },
87
86
88 show: function(point, element) {
87 show: function(point, element) {
89 if(!this.drops.length) return;
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 this.drops.each( function(drop) {
91 this.drops.each( function(drop) {
94 if(Droppables.isAffected(point, element, drop))
92 if(Droppables.isAffected(point, element, drop))
95 affected.push(drop);
93 affected.push(drop);
96 });
94 });
97
95
98 if(affected.length>0) {
96 if(affected.length>0)
99 drop = Droppables.findDeepestChild(affected);
97 drop = Droppables.findDeepestChild(affected);
98
99 if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
100 if (drop) {
100 Position.within(drop.element, point[0], point[1]);
101 Position.within(drop.element, point[0], point[1]);
101 if(drop.onHover)
102 if(drop.onHover)
102 drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
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
108 fire: function(event, element) {
109 fire: function(event, element) {
109 if(!this.last_active) return;
110 if(!this.last_active) return;
110 Position.prepare();
111 Position.prepare();
111
112
112 if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
113 if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
113 if (this.last_active.onDrop)
114 if (this.last_active.onDrop) {
114 this.last_active.onDrop(element, this.last_active.element, event);
115 this.last_active.onDrop(element, this.last_active.element, event);
116 return true;
117 }
115 },
118 },
116
119
117 reset: function() {
120 reset: function() {
118 if(this.last_active)
121 if(this.last_active)
119 this.deactivate(this.last_active);
122 this.deactivate(this.last_active);
120 }
123 }
121 }
124 }
122
125
123 var Draggables = {
126 var Draggables = {
124 drags: [],
127 drags: [],
125 observers: [],
128 observers: [],
126
129
127 register: function(draggable) {
130 register: function(draggable) {
128 if(this.drags.length == 0) {
131 if(this.drags.length == 0) {
129 this.eventMouseUp = this.endDrag.bindAsEventListener(this);
132 this.eventMouseUp = this.endDrag.bindAsEventListener(this);
130 this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
133 this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
131 this.eventKeypress = this.keyPress.bindAsEventListener(this);
134 this.eventKeypress = this.keyPress.bindAsEventListener(this);
132
135
133 Event.observe(document, "mouseup", this.eventMouseUp);
136 Event.observe(document, "mouseup", this.eventMouseUp);
134 Event.observe(document, "mousemove", this.eventMouseMove);
137 Event.observe(document, "mousemove", this.eventMouseMove);
135 Event.observe(document, "keypress", this.eventKeypress);
138 Event.observe(document, "keypress", this.eventKeypress);
136 }
139 }
137 this.drags.push(draggable);
140 this.drags.push(draggable);
138 },
141 },
139
142
140 unregister: function(draggable) {
143 unregister: function(draggable) {
141 this.drags = this.drags.reject(function(d) { return d==draggable });
144 this.drags = this.drags.reject(function(d) { return d==draggable });
142 if(this.drags.length == 0) {
145 if(this.drags.length == 0) {
143 Event.stopObserving(document, "mouseup", this.eventMouseUp);
146 Event.stopObserving(document, "mouseup", this.eventMouseUp);
144 Event.stopObserving(document, "mousemove", this.eventMouseMove);
147 Event.stopObserving(document, "mousemove", this.eventMouseMove);
145 Event.stopObserving(document, "keypress", this.eventKeypress);
148 Event.stopObserving(document, "keypress", this.eventKeypress);
146 }
149 }
147 },
150 },
148
151
149 activate: function(draggable) {
152 activate: function(draggable) {
150 if(draggable.options.delay) {
153 if(draggable.options.delay) {
151 this._timeout = setTimeout(function() {
154 this._timeout = setTimeout(function() {
152 Draggables._timeout = null;
155 Draggables._timeout = null;
153 window.focus();
156 window.focus();
154 Draggables.activeDraggable = draggable;
157 Draggables.activeDraggable = draggable;
155 }.bind(this), draggable.options.delay);
158 }.bind(this), draggable.options.delay);
156 } else {
159 } else {
157 window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
160 window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
158 this.activeDraggable = draggable;
161 this.activeDraggable = draggable;
159 }
162 }
160 },
163 },
161
164
162 deactivate: function() {
165 deactivate: function() {
163 this.activeDraggable = null;
166 this.activeDraggable = null;
164 },
167 },
165
168
166 updateDrag: function(event) {
169 updateDrag: function(event) {
167 if(!this.activeDraggable) return;
170 if(!this.activeDraggable) return;
168 var pointer = [Event.pointerX(event), Event.pointerY(event)];
171 var pointer = [Event.pointerX(event), Event.pointerY(event)];
169 // Mozilla-based browsers fire successive mousemove events with
172 // Mozilla-based browsers fire successive mousemove events with
170 // the same coordinates, prevent needless redrawing (moz bug?)
173 // the same coordinates, prevent needless redrawing (moz bug?)
171 if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
174 if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
172 this._lastPointer = pointer;
175 this._lastPointer = pointer;
173
176
174 this.activeDraggable.updateDrag(event, pointer);
177 this.activeDraggable.updateDrag(event, pointer);
175 },
178 },
176
179
177 endDrag: function(event) {
180 endDrag: function(event) {
178 if(this._timeout) {
181 if(this._timeout) {
179 clearTimeout(this._timeout);
182 clearTimeout(this._timeout);
180 this._timeout = null;
183 this._timeout = null;
181 }
184 }
182 if(!this.activeDraggable) return;
185 if(!this.activeDraggable) return;
183 this._lastPointer = null;
186 this._lastPointer = null;
184 this.activeDraggable.endDrag(event);
187 this.activeDraggable.endDrag(event);
185 this.activeDraggable = null;
188 this.activeDraggable = null;
186 },
189 },
187
190
188 keyPress: function(event) {
191 keyPress: function(event) {
189 if(this.activeDraggable)
192 if(this.activeDraggable)
190 this.activeDraggable.keyPress(event);
193 this.activeDraggable.keyPress(event);
191 },
194 },
192
195
193 addObserver: function(observer) {
196 addObserver: function(observer) {
194 this.observers.push(observer);
197 this.observers.push(observer);
195 this._cacheObserverCallbacks();
198 this._cacheObserverCallbacks();
196 },
199 },
197
200
198 removeObserver: function(element) { // element instead of observer fixes mem leaks
201 removeObserver: function(element) { // element instead of observer fixes mem leaks
199 this.observers = this.observers.reject( function(o) { return o.element==element });
202 this.observers = this.observers.reject( function(o) { return o.element==element });
200 this._cacheObserverCallbacks();
203 this._cacheObserverCallbacks();
201 },
204 },
202
205
203 notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
206 notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
204 if(this[eventName+'Count'] > 0)
207 if(this[eventName+'Count'] > 0)
205 this.observers.each( function(o) {
208 this.observers.each( function(o) {
206 if(o[eventName]) o[eventName](eventName, draggable, event);
209 if(o[eventName]) o[eventName](eventName, draggable, event);
207 });
210 });
208 if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
211 if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
209 },
212 },
210
213
211 _cacheObserverCallbacks: function() {
214 _cacheObserverCallbacks: function() {
212 ['onStart','onEnd','onDrag'].each( function(eventName) {
215 ['onStart','onEnd','onDrag'].each( function(eventName) {
213 Draggables[eventName+'Count'] = Draggables.observers.select(
216 Draggables[eventName+'Count'] = Draggables.observers.select(
214 function(o) { return o[eventName]; }
217 function(o) { return o[eventName]; }
215 ).length;
218 ).length;
216 });
219 });
217 }
220 }
218 }
221 }
219
222
220 /*--------------------------------------------------------------------------*/
223 /*--------------------------------------------------------------------------*/
221
224
222 var Draggable = Class.create();
225 var Draggable = Class.create({
223 Draggable._dragging = {};
224
225 Draggable.prototype = {
226 initialize: function(element) {
226 initialize: function(element) {
227 var defaults = {
227 var defaults = {
228 handle: false,
228 handle: false,
229 reverteffect: function(element, top_offset, left_offset) {
229 reverteffect: function(element, top_offset, left_offset) {
230 var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
230 var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
231 new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
231 new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
232 queue: {scope:'_draggable', position:'end'}
232 queue: {scope:'_draggable', position:'end'}
233 });
233 });
234 },
234 },
235 endeffect: function(element) {
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 new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
237 new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
238 queue: {scope:'_draggable', position:'end'},
238 queue: {scope:'_draggable', position:'end'},
239 afterFinish: function(){
239 afterFinish: function(){
240 Draggable._dragging[element] = false
240 Draggable._dragging[element] = false
241 }
241 }
242 });
242 });
243 },
243 },
244 zindex: 1000,
244 zindex: 1000,
245 revert: false,
245 revert: false,
246 quiet: false,
246 scroll: false,
247 scroll: false,
247 scrollSensitivity: 20,
248 scrollSensitivity: 20,
248 scrollSpeed: 15,
249 scrollSpeed: 15,
249 snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
250 snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
250 delay: 0
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 Object.extend(defaults, {
255 Object.extend(defaults, {
255 starteffect: function(element) {
256 starteffect: function(element) {
256 element._opacity = Element.getOpacity(element);
257 element._opacity = Element.getOpacity(element);
257 Draggable._dragging[element] = true;
258 Draggable._dragging[element] = true;
258 new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
259 new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
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 this.element = $(element);
265 this.element = $(element);
265
266
266 if(options.handle && (typeof options.handle == 'string'))
267 if(options.handle && Object.isString(options.handle))
267 this.handle = this.element.down('.'+options.handle, 0);
268 this.handle = this.element.down('.'+options.handle, 0);
268
269
269 if(!this.handle) this.handle = $(options.handle);
270 if(!this.handle) this.handle = $(options.handle);
270 if(!this.handle) this.handle = this.element;
271 if(!this.handle) this.handle = this.element;
271
272
272 if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
273 if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
273 options.scroll = $(options.scroll);
274 options.scroll = $(options.scroll);
274 this._isScrollChild = Element.childOf(this.element, options.scroll);
275 this._isScrollChild = Element.childOf(this.element, options.scroll);
275 }
276 }
276
277
277 Element.makePositioned(this.element); // fix IE
278 Element.makePositioned(this.element); // fix IE
278
279
279 this.delta = this.currentDelta();
280 this.options = options;
280 this.options = options;
281 this.dragging = false;
281 this.dragging = false;
282
282
283 this.eventMouseDown = this.initDrag.bindAsEventListener(this);
283 this.eventMouseDown = this.initDrag.bindAsEventListener(this);
284 Event.observe(this.handle, "mousedown", this.eventMouseDown);
284 Event.observe(this.handle, "mousedown", this.eventMouseDown);
285
285
286 Draggables.register(this);
286 Draggables.register(this);
287 },
287 },
288
288
289 destroy: function() {
289 destroy: function() {
290 Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
290 Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
291 Draggables.unregister(this);
291 Draggables.unregister(this);
292 },
292 },
293
293
294 currentDelta: function() {
294 currentDelta: function() {
295 return([
295 return([
296 parseInt(Element.getStyle(this.element,'left') || '0'),
296 parseInt(Element.getStyle(this.element,'left') || '0'),
297 parseInt(Element.getStyle(this.element,'top') || '0')]);
297 parseInt(Element.getStyle(this.element,'top') || '0')]);
298 },
298 },
299
299
300 initDrag: function(event) {
300 initDrag: function(event) {
301 if(typeof Draggable._dragging[this.element] != 'undefined' &&
301 if(!Object.isUndefined(Draggable._dragging[this.element]) &&
302 Draggable._dragging[this.element]) return;
302 Draggable._dragging[this.element]) return;
303 if(Event.isLeftClick(event)) {
303 if(Event.isLeftClick(event)) {
304 // abort on form elements, fixes a Firefox issue
304 // abort on form elements, fixes a Firefox issue
305 var src = Event.element(event);
305 var src = Event.element(event);
306 if(src.tagName && (
306 if((tag_name = src.tagName.toUpperCase()) && (
307 src.tagName=='INPUT' ||
307 tag_name=='INPUT' ||
308 src.tagName=='SELECT' ||
308 tag_name=='SELECT' ||
309 src.tagName=='OPTION' ||
309 tag_name=='OPTION' ||
310 src.tagName=='BUTTON' ||
310 tag_name=='BUTTON' ||
311 src.tagName=='TEXTAREA')) return;
311 tag_name=='TEXTAREA')) return;
312
312
313 var pointer = [Event.pointerX(event), Event.pointerY(event)];
313 var pointer = [Event.pointerX(event), Event.pointerY(event)];
314 var pos = Position.cumulativeOffset(this.element);
314 var pos = Position.cumulativeOffset(this.element);
315 this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
315 this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
316
316
317 Draggables.activate(this);
317 Draggables.activate(this);
318 Event.stop(event);
318 Event.stop(event);
319 }
319 }
320 },
320 },
321
321
322 startDrag: function(event) {
322 startDrag: function(event) {
323 this.dragging = true;
323 this.dragging = true;
324 if(!this.delta)
325 this.delta = this.currentDelta();
324
326
325 if(this.options.zindex) {
327 if(this.options.zindex) {
326 this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
328 this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
327 this.element.style.zIndex = this.options.zindex;
329 this.element.style.zIndex = this.options.zindex;
328 }
330 }
329
331
330 if(this.options.ghosting) {
332 if(this.options.ghosting) {
331 this._clone = this.element.cloneNode(true);
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 this.element.parentNode.insertBefore(this._clone, this.element);
337 this.element.parentNode.insertBefore(this._clone, this.element);
334 }
338 }
335
339
336 if(this.options.scroll) {
340 if(this.options.scroll) {
337 if (this.options.scroll == window) {
341 if (this.options.scroll == window) {
338 var where = this._getWindowScroll(this.options.scroll);
342 var where = this._getWindowScroll(this.options.scroll);
339 this.originalScrollLeft = where.left;
343 this.originalScrollLeft = where.left;
340 this.originalScrollTop = where.top;
344 this.originalScrollTop = where.top;
341 } else {
345 } else {
342 this.originalScrollLeft = this.options.scroll.scrollLeft;
346 this.originalScrollLeft = this.options.scroll.scrollLeft;
343 this.originalScrollTop = this.options.scroll.scrollTop;
347 this.originalScrollTop = this.options.scroll.scrollTop;
344 }
348 }
345 }
349 }
346
350
347 Draggables.notify('onStart', this, event);
351 Draggables.notify('onStart', this, event);
348
352
349 if(this.options.starteffect) this.options.starteffect(this.element);
353 if(this.options.starteffect) this.options.starteffect(this.element);
350 },
354 },
351
355
352 updateDrag: function(event, pointer) {
356 updateDrag: function(event, pointer) {
353 if(!this.dragging) this.startDrag(event);
357 if(!this.dragging) this.startDrag(event);
354 Position.prepare();
358
355 Droppables.show(pointer, this.element);
359 if(!this.options.quiet){
360 Position.prepare();
361 Droppables.show(pointer, this.element);
362 }
363
356 Draggables.notify('onDrag', this, event);
364 Draggables.notify('onDrag', this, event);
357
365
358 this.draw(pointer);
366 this.draw(pointer);
359 if(this.options.change) this.options.change(this);
367 if(this.options.change) this.options.change(this);
360
368
361 if(this.options.scroll) {
369 if(this.options.scroll) {
362 this.stopScrolling();
370 this.stopScrolling();
363
371
364 var p;
372 var p;
365 if (this.options.scroll == window) {
373 if (this.options.scroll == window) {
366 with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
374 with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
367 } else {
375 } else {
368 p = Position.page(this.options.scroll);
376 p = Position.page(this.options.scroll);
369 p[0] += this.options.scroll.scrollLeft + Position.deltaX;
377 p[0] += this.options.scroll.scrollLeft + Position.deltaX;
370 p[1] += this.options.scroll.scrollTop + Position.deltaY;
378 p[1] += this.options.scroll.scrollTop + Position.deltaY;
371 p.push(p[0]+this.options.scroll.offsetWidth);
379 p.push(p[0]+this.options.scroll.offsetWidth);
372 p.push(p[1]+this.options.scroll.offsetHeight);
380 p.push(p[1]+this.options.scroll.offsetHeight);
373 }
381 }
374 var speed = [0,0];
382 var speed = [0,0];
375 if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
383 if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
376 if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
384 if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
377 if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
385 if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
378 if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
386 if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
379 this.startScrolling(speed);
387 this.startScrolling(speed);
380 }
388 }
381
389
382 // fix AppleWebKit rendering
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 Event.stop(event);
393 Event.stop(event);
386 },
394 },
387
395
388 finishDrag: function(event, success) {
396 finishDrag: function(event, success) {
389 this.dragging = false;
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 if(this.options.ghosting) {
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 Element.remove(this._clone);
409 Element.remove(this._clone);
394 this._clone = null;
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 Draggables.notify('onEnd', this, event);
419 Draggables.notify('onEnd', this, event);
399
420
400 var revert = this.options.revert;
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 var d = this.currentDelta();
424 var d = this.currentDelta();
404 if(revert && this.options.reverteffect) {
425 if(revert && this.options.reverteffect) {
405 this.options.reverteffect(this.element,
426 if (dropped == 0 || revert != 'failure')
406 d[1]-this.delta[1], d[0]-this.delta[0]);
427 this.options.reverteffect(this.element,
428 d[1]-this.delta[1], d[0]-this.delta[0]);
407 } else {
429 } else {
408 this.delta = d;
430 this.delta = d;
409 }
431 }
410
432
411 if(this.options.zindex)
433 if(this.options.zindex)
412 this.element.style.zIndex = this.originalZ;
434 this.element.style.zIndex = this.originalZ;
413
435
414 if(this.options.endeffect)
436 if(this.options.endeffect)
415 this.options.endeffect(this.element);
437 this.options.endeffect(this.element);
416
438
417 Draggables.deactivate(this);
439 Draggables.deactivate(this);
418 Droppables.reset();
440 Droppables.reset();
419 },
441 },
420
442
421 keyPress: function(event) {
443 keyPress: function(event) {
422 if(event.keyCode!=Event.KEY_ESC) return;
444 if(event.keyCode!=Event.KEY_ESC) return;
423 this.finishDrag(event, false);
445 this.finishDrag(event, false);
424 Event.stop(event);
446 Event.stop(event);
425 },
447 },
426
448
427 endDrag: function(event) {
449 endDrag: function(event) {
428 if(!this.dragging) return;
450 if(!this.dragging) return;
429 this.stopScrolling();
451 this.stopScrolling();
430 this.finishDrag(event, true);
452 this.finishDrag(event, true);
431 Event.stop(event);
453 Event.stop(event);
432 },
454 },
433
455
434 draw: function(point) {
456 draw: function(point) {
435 var pos = Position.cumulativeOffset(this.element);
457 var pos = Position.cumulativeOffset(this.element);
436 if(this.options.ghosting) {
458 if(this.options.ghosting) {
437 var r = Position.realOffset(this.element);
459 var r = Position.realOffset(this.element);
438 pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
460 pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
439 }
461 }
440
462
441 var d = this.currentDelta();
463 var d = this.currentDelta();
442 pos[0] -= d[0]; pos[1] -= d[1];
464 pos[0] -= d[0]; pos[1] -= d[1];
443
465
444 if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
466 if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
445 pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
467 pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
446 pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
468 pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
447 }
469 }
448
470
449 var p = [0,1].map(function(i){
471 var p = [0,1].map(function(i){
450 return (point[i]-pos[i]-this.offset[i])
472 return (point[i]-pos[i]-this.offset[i])
451 }.bind(this));
473 }.bind(this));
452
474
453 if(this.options.snap) {
475 if(this.options.snap) {
454 if(typeof this.options.snap == 'function') {
476 if(Object.isFunction(this.options.snap)) {
455 p = this.options.snap(p[0],p[1],this);
477 p = this.options.snap(p[0],p[1],this);
456 } else {
478 } else {
457 if(this.options.snap instanceof Array) {
479 if(Object.isArray(this.options.snap)) {
458 p = p.map( function(v, i) {
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 } else {
482 } else {
461 p = p.map( function(v) {
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
466 var style = this.element.style;
488 var style = this.element.style;
467 if((!this.options.constraint) || (this.options.constraint=='horizontal'))
489 if((!this.options.constraint) || (this.options.constraint=='horizontal'))
468 style.left = p[0] + "px";
490 style.left = p[0] + "px";
469 if((!this.options.constraint) || (this.options.constraint=='vertical'))
491 if((!this.options.constraint) || (this.options.constraint=='vertical'))
470 style.top = p[1] + "px";
492 style.top = p[1] + "px";
471
493
472 if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
494 if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
473 },
495 },
474
496
475 stopScrolling: function() {
497 stopScrolling: function() {
476 if(this.scrollInterval) {
498 if(this.scrollInterval) {
477 clearInterval(this.scrollInterval);
499 clearInterval(this.scrollInterval);
478 this.scrollInterval = null;
500 this.scrollInterval = null;
479 Draggables._lastScrollPointer = null;
501 Draggables._lastScrollPointer = null;
480 }
502 }
481 },
503 },
482
504
483 startScrolling: function(speed) {
505 startScrolling: function(speed) {
484 if(!(speed[0] || speed[1])) return;
506 if(!(speed[0] || speed[1])) return;
485 this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
507 this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
486 this.lastScrolled = new Date();
508 this.lastScrolled = new Date();
487 this.scrollInterval = setInterval(this.scroll.bind(this), 10);
509 this.scrollInterval = setInterval(this.scroll.bind(this), 10);
488 },
510 },
489
511
490 scroll: function() {
512 scroll: function() {
491 var current = new Date();
513 var current = new Date();
492 var delta = current - this.lastScrolled;
514 var delta = current - this.lastScrolled;
493 this.lastScrolled = current;
515 this.lastScrolled = current;
494 if(this.options.scroll == window) {
516 if(this.options.scroll == window) {
495 with (this._getWindowScroll(this.options.scroll)) {
517 with (this._getWindowScroll(this.options.scroll)) {
496 if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
518 if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
497 var d = delta / 1000;
519 var d = delta / 1000;
498 this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
520 this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
499 }
521 }
500 }
522 }
501 } else {
523 } else {
502 this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
524 this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
503 this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
525 this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
504 }
526 }
505
527
506 Position.prepare();
528 Position.prepare();
507 Droppables.show(Draggables._lastPointer, this.element);
529 Droppables.show(Draggables._lastPointer, this.element);
508 Draggables.notify('onDrag', this);
530 Draggables.notify('onDrag', this);
509 if (this._isScrollChild) {
531 if (this._isScrollChild) {
510 Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
532 Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
511 Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
533 Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
512 Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
534 Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
513 if (Draggables._lastScrollPointer[0] < 0)
535 if (Draggables._lastScrollPointer[0] < 0)
514 Draggables._lastScrollPointer[0] = 0;
536 Draggables._lastScrollPointer[0] = 0;
515 if (Draggables._lastScrollPointer[1] < 0)
537 if (Draggables._lastScrollPointer[1] < 0)
516 Draggables._lastScrollPointer[1] = 0;
538 Draggables._lastScrollPointer[1] = 0;
517 this.draw(Draggables._lastScrollPointer);
539 this.draw(Draggables._lastScrollPointer);
518 }
540 }
519
541
520 if(this.options.change) this.options.change(this);
542 if(this.options.change) this.options.change(this);
521 },
543 },
522
544
523 _getWindowScroll: function(w) {
545 _getWindowScroll: function(w) {
524 var T, L, W, H;
546 var T, L, W, H;
525 with (w.document) {
547 with (w.document) {
526 if (w.document.documentElement && documentElement.scrollTop) {
548 if (w.document.documentElement && documentElement.scrollTop) {
527 T = documentElement.scrollTop;
549 T = documentElement.scrollTop;
528 L = documentElement.scrollLeft;
550 L = documentElement.scrollLeft;
529 } else if (w.document.body) {
551 } else if (w.document.body) {
530 T = body.scrollTop;
552 T = body.scrollTop;
531 L = body.scrollLeft;
553 L = body.scrollLeft;
532 }
554 }
533 if (w.innerWidth) {
555 if (w.innerWidth) {
534 W = w.innerWidth;
556 W = w.innerWidth;
535 H = w.innerHeight;
557 H = w.innerHeight;
536 } else if (w.document.documentElement && documentElement.clientWidth) {
558 } else if (w.document.documentElement && documentElement.clientWidth) {
537 W = documentElement.clientWidth;
559 W = documentElement.clientWidth;
538 H = documentElement.clientHeight;
560 H = documentElement.clientHeight;
539 } else {
561 } else {
540 W = body.offsetWidth;
562 W = body.offsetWidth;
541 H = body.offsetHeight
563 H = body.offsetHeight
542 }
564 }
543 }
565 }
544 return { top: T, left: L, width: W, height: H };
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();
574 var SortableObserver = Class.create({
551 SortableObserver.prototype = {
552 initialize: function(element, observer) {
575 initialize: function(element, observer) {
553 this.element = $(element);
576 this.element = $(element);
554 this.observer = observer;
577 this.observer = observer;
555 this.lastValue = Sortable.serialize(this.element);
578 this.lastValue = Sortable.serialize(this.element);
556 },
579 },
557
580
558 onStart: function() {
581 onStart: function() {
559 this.lastValue = Sortable.serialize(this.element);
582 this.lastValue = Sortable.serialize(this.element);
560 },
583 },
561
584
562 onEnd: function() {
585 onEnd: function() {
563 Sortable.unmark();
586 Sortable.unmark();
564 if(this.lastValue != Sortable.serialize(this.element))
587 if(this.lastValue != Sortable.serialize(this.element))
565 this.observer(this.element)
588 this.observer(this.element)
566 }
589 }
567 }
590 });
568
591
569 var Sortable = {
592 var Sortable = {
570 SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
593 SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
571
594
572 sortables: {},
595 sortables: { },
573
596
574 _findRootElement: function(element) {
597 _findRootElement: function(element) {
575 while (element.tagName != "BODY") {
598 while (element.tagName.toUpperCase() != "BODY") {
576 if(element.id && Sortable.sortables[element.id]) return element;
599 if(element.id && Sortable.sortables[element.id]) return element;
577 element = element.parentNode;
600 element = element.parentNode;
578 }
601 }
579 },
602 },
580
603
581 options: function(element) {
604 options: function(element) {
582 element = Sortable._findRootElement($(element));
605 element = Sortable._findRootElement($(element));
583 if(!element) return;
606 if(!element) return;
584 return Sortable.sortables[element.id];
607 return Sortable.sortables[element.id];
585 },
608 },
586
609
587 destroy: function(element){
610 destroy: function(element){
588 var s = Sortable.options(element);
611 var s = Sortable.options(element);
589
612
590 if(s) {
613 if(s) {
591 Draggables.removeObserver(s.element);
614 Draggables.removeObserver(s.element);
592 s.droppables.each(function(d){ Droppables.remove(d) });
615 s.droppables.each(function(d){ Droppables.remove(d) });
593 s.draggables.invoke('destroy');
616 s.draggables.invoke('destroy');
594
617
595 delete Sortable.sortables[s.element.id];
618 delete Sortable.sortables[s.element.id];
596 }
619 }
597 },
620 },
598
621
599 create: function(element) {
622 create: function(element) {
600 element = $(element);
623 element = $(element);
601 var options = Object.extend({
624 var options = Object.extend({
602 element: element,
625 element: element,
603 tag: 'li', // assumes li children, override with tag: 'tagname'
626 tag: 'li', // assumes li children, override with tag: 'tagname'
604 dropOnEmpty: false,
627 dropOnEmpty: false,
605 tree: false,
628 tree: false,
606 treeTag: 'ul',
629 treeTag: 'ul',
607 overlap: 'vertical', // one of 'vertical', 'horizontal'
630 overlap: 'vertical', // one of 'vertical', 'horizontal'
608 constraint: 'vertical', // one of 'vertical', 'horizontal', false
631 constraint: 'vertical', // one of 'vertical', 'horizontal', false
609 containment: element, // also takes array of elements (or id's); or false
632 containment: element, // also takes array of elements (or id's); or false
610 handle: false, // or a CSS class
633 handle: false, // or a CSS class
611 only: false,
634 only: false,
612 delay: 0,
635 delay: 0,
613 hoverclass: null,
636 hoverclass: null,
614 ghosting: false,
637 ghosting: false,
638 quiet: false,
615 scroll: false,
639 scroll: false,
616 scrollSensitivity: 20,
640 scrollSensitivity: 20,
617 scrollSpeed: 15,
641 scrollSpeed: 15,
618 format: this.SERIALIZE_RULE,
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 onChange: Prototype.emptyFunction,
649 onChange: Prototype.emptyFunction,
620 onUpdate: Prototype.emptyFunction
650 onUpdate: Prototype.emptyFunction
621 }, arguments[1] || {});
651 }, arguments[1] || { });
622
652
623 // clear any old sortable with same element
653 // clear any old sortable with same element
624 this.destroy(element);
654 this.destroy(element);
625
655
626 // build options for the draggables
656 // build options for the draggables
627 var options_for_draggable = {
657 var options_for_draggable = {
628 revert: true,
658 revert: true,
659 quiet: options.quiet,
629 scroll: options.scroll,
660 scroll: options.scroll,
630 scrollSpeed: options.scrollSpeed,
661 scrollSpeed: options.scrollSpeed,
631 scrollSensitivity: options.scrollSensitivity,
662 scrollSensitivity: options.scrollSensitivity,
632 delay: options.delay,
663 delay: options.delay,
633 ghosting: options.ghosting,
664 ghosting: options.ghosting,
634 constraint: options.constraint,
665 constraint: options.constraint,
635 handle: options.handle };
666 handle: options.handle };
636
667
637 if(options.starteffect)
668 if(options.starteffect)
638 options_for_draggable.starteffect = options.starteffect;
669 options_for_draggable.starteffect = options.starteffect;
639
670
640 if(options.reverteffect)
671 if(options.reverteffect)
641 options_for_draggable.reverteffect = options.reverteffect;
672 options_for_draggable.reverteffect = options.reverteffect;
642 else
673 else
643 if(options.ghosting) options_for_draggable.reverteffect = function(element) {
674 if(options.ghosting) options_for_draggable.reverteffect = function(element) {
644 element.style.top = 0;
675 element.style.top = 0;
645 element.style.left = 0;
676 element.style.left = 0;
646 };
677 };
647
678
648 if(options.endeffect)
679 if(options.endeffect)
649 options_for_draggable.endeffect = options.endeffect;
680 options_for_draggable.endeffect = options.endeffect;
650
681
651 if(options.zindex)
682 if(options.zindex)
652 options_for_draggable.zindex = options.zindex;
683 options_for_draggable.zindex = options.zindex;
653
684
654 // build options for the droppables
685 // build options for the droppables
655 var options_for_droppable = {
686 var options_for_droppable = {
656 overlap: options.overlap,
687 overlap: options.overlap,
657 containment: options.containment,
688 containment: options.containment,
658 tree: options.tree,
689 tree: options.tree,
659 hoverclass: options.hoverclass,
690 hoverclass: options.hoverclass,
660 onHover: Sortable.onHover
691 onHover: Sortable.onHover
661 }
692 }
662
693
663 var options_for_tree = {
694 var options_for_tree = {
664 onHover: Sortable.onEmptyHover,
695 onHover: Sortable.onEmptyHover,
665 overlap: options.overlap,
696 overlap: options.overlap,
666 containment: options.containment,
697 containment: options.containment,
667 hoverclass: options.hoverclass
698 hoverclass: options.hoverclass
668 }
699 }
669
700
670 // fix for gecko engine
701 // fix for gecko engine
671 Element.cleanWhitespace(element);
702 Element.cleanWhitespace(element);
672
703
673 options.draggables = [];
704 options.draggables = [];
674 options.droppables = [];
705 options.droppables = [];
675
706
676 // drop on empty handling
707 // drop on empty handling
677 if(options.dropOnEmpty || options.tree) {
708 if(options.dropOnEmpty || options.tree) {
678 Droppables.add(element, options_for_tree);
709 Droppables.add(element, options_for_tree);
679 options.droppables.push(element);
710 options.droppables.push(element);
680 }
711 }
681
712
682 (this.findElements(element, options) || []).each( function(e) {
713 (options.elements || this.findElements(element, options) || []).each( function(e,i) {
683 // handles are per-draggable
714 var handle = options.handles ? $(options.handles[i]) :
684 var handle = options.handle ?
715 (options.handle ? $(e).select('.' + options.handle)[0] : e);
685 $(e).down('.'+options.handle,0) : e;
686 options.draggables.push(
716 options.draggables.push(
687 new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
717 new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
688 Droppables.add(e, options_for_droppable);
718 Droppables.add(e, options_for_droppable);
689 if(options.tree) e.treeNode = element;
719 if(options.tree) e.treeNode = element;
690 options.droppables.push(e);
720 options.droppables.push(e);
691 });
721 });
692
722
693 if(options.tree) {
723 if(options.tree) {
694 (Sortable.findTreeElements(element, options) || []).each( function(e) {
724 (Sortable.findTreeElements(element, options) || []).each( function(e) {
695 Droppables.add(e, options_for_tree);
725 Droppables.add(e, options_for_tree);
696 e.treeNode = element;
726 e.treeNode = element;
697 options.droppables.push(e);
727 options.droppables.push(e);
698 });
728 });
699 }
729 }
700
730
701 // keep reference
731 // keep reference
702 this.sortables[element.id] = options;
732 this.sortables[element.id] = options;
703
733
704 // for onupdate
734 // for onupdate
705 Draggables.addObserver(new SortableObserver(element, options.onUpdate));
735 Draggables.addObserver(new SortableObserver(element, options.onUpdate));
706
736
707 },
737 },
708
738
709 // return all suitable-for-sortable elements in a guaranteed order
739 // return all suitable-for-sortable elements in a guaranteed order
710 findElements: function(element, options) {
740 findElements: function(element, options) {
711 return Element.findChildren(
741 return Element.findChildren(
712 element, options.only, options.tree ? true : false, options.tag);
742 element, options.only, options.tree ? true : false, options.tag);
713 },
743 },
714
744
715 findTreeElements: function(element, options) {
745 findTreeElements: function(element, options) {
716 return Element.findChildren(
746 return Element.findChildren(
717 element, options.only, options.tree ? true : false, options.treeTag);
747 element, options.only, options.tree ? true : false, options.treeTag);
718 },
748 },
719
749
720 onHover: function(element, dropon, overlap) {
750 onHover: function(element, dropon, overlap) {
721 if(Element.isParent(dropon, element)) return;
751 if(Element.isParent(dropon, element)) return;
722
752
723 if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
753 if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
724 return;
754 return;
725 } else if(overlap>0.5) {
755 } else if(overlap>0.5) {
726 Sortable.mark(dropon, 'before');
756 Sortable.mark(dropon, 'before');
727 if(dropon.previousSibling != element) {
757 if(dropon.previousSibling != element) {
728 var oldParentNode = element.parentNode;
758 var oldParentNode = element.parentNode;
729 element.style.visibility = "hidden"; // fix gecko rendering
759 element.style.visibility = "hidden"; // fix gecko rendering
730 dropon.parentNode.insertBefore(element, dropon);
760 dropon.parentNode.insertBefore(element, dropon);
731 if(dropon.parentNode!=oldParentNode)
761 if(dropon.parentNode!=oldParentNode)
732 Sortable.options(oldParentNode).onChange(element);
762 Sortable.options(oldParentNode).onChange(element);
733 Sortable.options(dropon.parentNode).onChange(element);
763 Sortable.options(dropon.parentNode).onChange(element);
734 }
764 }
735 } else {
765 } else {
736 Sortable.mark(dropon, 'after');
766 Sortable.mark(dropon, 'after');
737 var nextElement = dropon.nextSibling || null;
767 var nextElement = dropon.nextSibling || null;
738 if(nextElement != element) {
768 if(nextElement != element) {
739 var oldParentNode = element.parentNode;
769 var oldParentNode = element.parentNode;
740 element.style.visibility = "hidden"; // fix gecko rendering
770 element.style.visibility = "hidden"; // fix gecko rendering
741 dropon.parentNode.insertBefore(element, nextElement);
771 dropon.parentNode.insertBefore(element, nextElement);
742 if(dropon.parentNode!=oldParentNode)
772 if(dropon.parentNode!=oldParentNode)
743 Sortable.options(oldParentNode).onChange(element);
773 Sortable.options(oldParentNode).onChange(element);
744 Sortable.options(dropon.parentNode).onChange(element);
774 Sortable.options(dropon.parentNode).onChange(element);
745 }
775 }
746 }
776 }
747 },
777 },
748
778
749 onEmptyHover: function(element, dropon, overlap) {
779 onEmptyHover: function(element, dropon, overlap) {
750 var oldParentNode = element.parentNode;
780 var oldParentNode = element.parentNode;
751 var droponOptions = Sortable.options(dropon);
781 var droponOptions = Sortable.options(dropon);
752
782
753 if(!Element.isParent(dropon, element)) {
783 if(!Element.isParent(dropon, element)) {
754 var index;
784 var index;
755
785
756 var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
786 var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
757 var child = null;
787 var child = null;
758
788
759 if(children) {
789 if(children) {
760 var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
790 var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
761
791
762 for (index = 0; index < children.length; index += 1) {
792 for (index = 0; index < children.length; index += 1) {
763 if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
793 if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
764 offset -= Element.offsetSize (children[index], droponOptions.overlap);
794 offset -= Element.offsetSize (children[index], droponOptions.overlap);
765 } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
795 } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
766 child = index + 1 < children.length ? children[index + 1] : null;
796 child = index + 1 < children.length ? children[index + 1] : null;
767 break;
797 break;
768 } else {
798 } else {
769 child = children[index];
799 child = children[index];
770 break;
800 break;
771 }
801 }
772 }
802 }
773 }
803 }
774
804
775 dropon.insertBefore(element, child);
805 dropon.insertBefore(element, child);
776
806
777 Sortable.options(oldParentNode).onChange(element);
807 Sortable.options(oldParentNode).onChange(element);
778 droponOptions.onChange(element);
808 droponOptions.onChange(element);
779 }
809 }
780 },
810 },
781
811
782 unmark: function() {
812 unmark: function() {
783 if(Sortable._marker) Sortable._marker.hide();
813 if(Sortable._marker) Sortable._marker.hide();
784 },
814 },
785
815
786 mark: function(dropon, position) {
816 mark: function(dropon, position) {
787 // mark on ghosting only
817 // mark on ghosting only
788 var sortable = Sortable.options(dropon.parentNode);
818 var sortable = Sortable.options(dropon.parentNode);
789 if(sortable && !sortable.ghosting) return;
819 if(sortable && !sortable.ghosting) return;
790
820
791 if(!Sortable._marker) {
821 if(!Sortable._marker) {
792 Sortable._marker =
822 Sortable._marker =
793 ($('dropmarker') || Element.extend(document.createElement('DIV'))).
823 ($('dropmarker') || Element.extend(document.createElement('DIV'))).
794 hide().addClassName('dropmarker').setStyle({position:'absolute'});
824 hide().addClassName('dropmarker').setStyle({position:'absolute'});
795 document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
825 document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
796 }
826 }
797 var offsets = Position.cumulativeOffset(dropon);
827 var offsets = Position.cumulativeOffset(dropon);
798 Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
828 Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
799
829
800 if(position=='after')
830 if(position=='after')
801 if(sortable.overlap == 'horizontal')
831 if(sortable.overlap == 'horizontal')
802 Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
832 Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
803 else
833 else
804 Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
834 Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
805
835
806 Sortable._marker.show();
836 Sortable._marker.show();
807 },
837 },
808
838
809 _tree: function(element, options, parent) {
839 _tree: function(element, options, parent) {
810 var children = Sortable.findElements(element, options) || [];
840 var children = Sortable.findElements(element, options) || [];
811
841
812 for (var i = 0; i < children.length; ++i) {
842 for (var i = 0; i < children.length; ++i) {
813 var match = children[i].id.match(options.format);
843 var match = children[i].id.match(options.format);
814
844
815 if (!match) continue;
845 if (!match) continue;
816
846
817 var child = {
847 var child = {
818 id: encodeURIComponent(match ? match[1] : null),
848 id: encodeURIComponent(match ? match[1] : null),
819 element: element,
849 element: element,
820 parent: parent,
850 parent: parent,
821 children: [],
851 children: [],
822 position: parent.children.length,
852 position: parent.children.length,
823 container: $(children[i]).down(options.treeTag)
853 container: $(children[i]).down(options.treeTag)
824 }
854 }
825
855
826 /* Get the element containing the children and recurse over it */
856 /* Get the element containing the children and recurse over it */
827 if (child.container)
857 if (child.container)
828 this._tree(child.container, options, child)
858 this._tree(child.container, options, child)
829
859
830 parent.children.push (child);
860 parent.children.push (child);
831 }
861 }
832
862
833 return parent;
863 return parent;
834 },
864 },
835
865
836 tree: function(element) {
866 tree: function(element) {
837 element = $(element);
867 element = $(element);
838 var sortableOptions = this.options(element);
868 var sortableOptions = this.options(element);
839 var options = Object.extend({
869 var options = Object.extend({
840 tag: sortableOptions.tag,
870 tag: sortableOptions.tag,
841 treeTag: sortableOptions.treeTag,
871 treeTag: sortableOptions.treeTag,
842 only: sortableOptions.only,
872 only: sortableOptions.only,
843 name: element.id,
873 name: element.id,
844 format: sortableOptions.format
874 format: sortableOptions.format
845 }, arguments[1] || {});
875 }, arguments[1] || { });
846
876
847 var root = {
877 var root = {
848 id: null,
878 id: null,
849 parent: null,
879 parent: null,
850 children: [],
880 children: [],
851 container: element,
881 container: element,
852 position: 0
882 position: 0
853 }
883 }
854
884
855 return Sortable._tree(element, options, root);
885 return Sortable._tree(element, options, root);
856 },
886 },
857
887
858 /* Construct a [i] index for a particular node */
888 /* Construct a [i] index for a particular node */
859 _constructIndex: function(node) {
889 _constructIndex: function(node) {
860 var index = '';
890 var index = '';
861 do {
891 do {
862 if (node.id) index = '[' + node.position + ']' + index;
892 if (node.id) index = '[' + node.position + ']' + index;
863 } while ((node = node.parent) != null);
893 } while ((node = node.parent) != null);
864 return index;
894 return index;
865 },
895 },
866
896
867 sequence: function(element) {
897 sequence: function(element) {
868 element = $(element);
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 return $(this.findElements(element, options) || []).map( function(item) {
901 return $(this.findElements(element, options) || []).map( function(item) {
872 return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
902 return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
873 });
903 });
874 },
904 },
875
905
876 setSequence: function(element, new_sequence) {
906 setSequence: function(element, new_sequence) {
877 element = $(element);
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 this.findElements(element, options).each( function(n) {
911 this.findElements(element, options).each( function(n) {
882 if (n.id.match(options.format))
912 if (n.id.match(options.format))
883 nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
913 nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
884 n.parentNode.removeChild(n);
914 n.parentNode.removeChild(n);
885 });
915 });
886
916
887 new_sequence.each(function(ident) {
917 new_sequence.each(function(ident) {
888 var n = nodeMap[ident];
918 var n = nodeMap[ident];
889 if (n) {
919 if (n) {
890 n[1].appendChild(n[0]);
920 n[1].appendChild(n[0]);
891 delete nodeMap[ident];
921 delete nodeMap[ident];
892 }
922 }
893 });
923 });
894 },
924 },
895
925
896 serialize: function(element) {
926 serialize: function(element) {
897 element = $(element);
927 element = $(element);
898 var options = Object.extend(Sortable.options(element), arguments[1] || {});
928 var options = Object.extend(Sortable.options(element), arguments[1] || { });
899 var name = encodeURIComponent(
929 var name = encodeURIComponent(
900 (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
930 (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
901
931
902 if (options.tree) {
932 if (options.tree) {
903 return Sortable.tree(element, arguments[1]).children.map( function (item) {
933 return Sortable.tree(element, arguments[1]).children.map( function (item) {
904 return [name + Sortable._constructIndex(item) + "[id]=" +
934 return [name + Sortable._constructIndex(item) + "[id]=" +
905 encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
935 encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
906 }).flatten().join('&');
936 }).flatten().join('&');
907 } else {
937 } else {
908 return Sortable.sequence(element, arguments[1]).map( function(item) {
938 return Sortable.sequence(element, arguments[1]).map( function(item) {
909 return name + "[]=" + encodeURIComponent(item);
939 return name + "[]=" + encodeURIComponent(item);
910 }).join('&');
940 }).join('&');
911 }
941 }
912 }
942 }
913 }
943 }
914
944
915 // Returns true if child is contained within element
945 // Returns true if child is contained within element
916 Element.isParent = function(child, element) {
946 Element.isParent = function(child, element) {
917 if (!child.parentNode || child == element) return false;
947 if (!child.parentNode || child == element) return false;
918 if (child.parentNode == element) return true;
948 if (child.parentNode == element) return true;
919 return Element.isParent(child.parentNode, element);
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 if(!element.hasChildNodes()) return null;
953 if(!element.hasChildNodes()) return null;
924 tagName = tagName.toUpperCase();
954 tagName = tagName.toUpperCase();
925 if(only) only = [only].flatten();
955 if(only) only = [only].flatten();
926 var elements = [];
956 var elements = [];
927 $A(element.childNodes).each( function(e) {
957 $A(element.childNodes).each( function(e) {
928 if(e.tagName && e.tagName.toUpperCase()==tagName &&
958 if(e.tagName && e.tagName.toUpperCase()==tagName &&
929 (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
959 (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
930 elements.push(e);
960 elements.push(e);
931 if(recursive) {
961 if(recursive) {
932 var grandchildren = Element.findChildren(e, only, recursive, tagName);
962 var grandchildren = Element.findChildren(e, only, recursive, tagName);
933 if(grandchildren) elements.push(grandchildren);
963 if(grandchildren) elements.push(grandchildren);
934 }
964 }
935 });
965 });
936
966
937 return (elements.length>0 ? elements.flatten() : []);
967 return (elements.length>0 ? elements.flatten() : []);
938 }
968 }
939
969
940 Element.offsetSize = function (element, type) {
970 Element.offsetSize = function (element, type) {
941 return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
971 return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
942 }
972 }
This diff has been collapsed as it changes many lines, (760 lines changed) Show them Hide them
@@ -1,1088 +1,1120
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 // Contributors:
2 // Contributors:
3 // Justin Palmer (http://encytemedia.com/)
3 // Justin Palmer (http://encytemedia.com/)
4 // Mark Pilgrim (http://diveintomark.org/)
4 // Mark Pilgrim (http://diveintomark.org/)
5 // Martin Bialasinki
5 // Martin Bialasinki
6 //
6 //
7 // script.aculo.us is freely distributable under the terms of an MIT-style license.
7 // script.aculo.us is freely distributable under the terms of an MIT-style license.
8 // For details, see the script.aculo.us web site: http://script.aculo.us/
8 // For details, see the script.aculo.us web site: http://script.aculo.us/
9
9
10 // converts rgb() and #xxx to #xxxxxx format,
10 // converts rgb() and #xxx to #xxxxxx format,
11 // returns self (or first argument) if not convertable
11 // returns self (or first argument) if not convertable
12 String.prototype.parseColor = function() {
12 String.prototype.parseColor = function() {
13 var color = '#';
13 var color = '#';
14 if(this.slice(0,4) == 'rgb(') {
14 if (this.slice(0,4) == 'rgb(') {
15 var cols = this.slice(4,this.length-1).split(',');
15 var cols = this.slice(4,this.length-1).split(',');
16 var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
16 var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
17 } else {
17 } else {
18 if(this.slice(0,1) == '#') {
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();
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();
20 if (this.length==7) color = this.toLowerCase();
21 }
21 }
22 }
22 }
23 return(color.length==7 ? color : (arguments[0] || this));
23 return (color.length==7 ? color : (arguments[0] || this));
24 }
24 };
25
25
26 /*--------------------------------------------------------------------------*/
26 /*--------------------------------------------------------------------------*/
27
27
28 Element.collectTextNodes = function(element) {
28 Element.collectTextNodes = function(element) {
29 return $A($(element).childNodes).collect( function(node) {
29 return $A($(element).childNodes).collect( function(node) {
30 return (node.nodeType==3 ? node.nodeValue :
30 return (node.nodeType==3 ? node.nodeValue :
31 (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
31 (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
32 }).flatten().join('');
32 }).flatten().join('');
33 }
33 };
34
34
35 Element.collectTextNodesIgnoreClass = function(element, className) {
35 Element.collectTextNodesIgnoreClass = function(element, className) {
36 return $A($(element).childNodes).collect( function(node) {
36 return $A($(element).childNodes).collect( function(node) {
37 return (node.nodeType==3 ? node.nodeValue :
37 return (node.nodeType==3 ? node.nodeValue :
38 ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
38 ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
39 Element.collectTextNodesIgnoreClass(node, className) : ''));
39 Element.collectTextNodesIgnoreClass(node, className) : ''));
40 }).flatten().join('');
40 }).flatten().join('');
41 }
41 };
42
42
43 Element.setContentZoom = function(element, percent) {
43 Element.setContentZoom = function(element, percent) {
44 element = $(element);
44 element = $(element);
45 element.setStyle({fontSize: (percent/100) + 'em'});
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 return element;
47 return element;
48 }
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 }
59
49
60 Element.setOpacity = function(element, value){
50 Element.getInlineOpacity = function(element){
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){
80 return $(element).style.opacity || '';
51 return $(element).style.opacity || '';
81 }
52 };
82
53
83 Element.forceRerendering = function(element) {
54 Element.forceRerendering = function(element) {
84 try {
55 try {
85 element = $(element);
56 element = $(element);
86 var n = document.createTextNode(' ');
57 var n = document.createTextNode(' ');
87 element.appendChild(n);
58 element.appendChild(n);
88 element.removeChild(n);
59 element.removeChild(n);
89 } catch(e) { }
60 } catch(e) { }
90 };
61 };
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 var Effect = {
65 var Effect = {
102 _elementDoesNotExistError: {
66 _elementDoesNotExistError: {
103 name: 'ElementDoesNotExistError',
67 name: 'ElementDoesNotExistError',
104 message: 'The specified DOM element does not exist, but is required for this effect to operate'
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 tagifyText: function(element) {
112 tagifyText: function(element) {
107 if(typeof Builder == 'undefined')
108 throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
109
110 var tagifyStyle = 'position:relative';
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 element = $(element);
116 element = $(element);
114 $A(element.childNodes).each( function(child) {
117 $A(element.childNodes).each( function(child) {
115 if(child.nodeType==3) {
118 if (child.nodeType==3) {
116 child.nodeValue.toArray().each( function(character) {
119 child.nodeValue.toArray().each( function(character) {
117 element.insertBefore(
120 element.insertBefore(
118 Builder.node('span',{style: tagifyStyle},
121 new Element('span', {style: tagifyStyle}).update(
119 character == ' ' ? String.fromCharCode(160) : character),
122 character == ' ' ? String.fromCharCode(160) : character),
120 child);
123 child);
121 });
124 });
122 Element.remove(child);
125 Element.remove(child);
123 }
126 }
124 });
127 });
125 },
128 },
126 multiple: function(element, effect) {
129 multiple: function(element, effect) {
127 var elements;
130 var elements;
128 if(((typeof element == 'object') ||
131 if (((typeof element == 'object') ||
129 (typeof element == 'function')) &&
132 Object.isFunction(element)) &&
130 (element.length))
133 (element.length))
131 elements = element;
134 elements = element;
132 else
135 else
133 elements = $(element).childNodes;
136 elements = $(element).childNodes;
134
137
135 var options = Object.extend({
138 var options = Object.extend({
136 speed: 0.1,
139 speed: 0.1,
137 delay: 0.0
140 delay: 0.0
138 }, arguments[2] || {});
141 }, arguments[2] || { });
139 var masterDelay = options.delay;
142 var masterDelay = options.delay;
140
143
141 $A(elements).each( function(element, index) {
144 $A(elements).each( function(element, index) {
142 new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
145 new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
143 });
146 });
144 },
147 },
145 PAIRS: {
148 PAIRS: {
146 'slide': ['SlideDown','SlideUp'],
149 'slide': ['SlideDown','SlideUp'],
147 'blind': ['BlindDown','BlindUp'],
150 'blind': ['BlindDown','BlindUp'],
148 'appear': ['Appear','Fade']
151 'appear': ['Appear','Fade']
149 },
152 },
150 toggle: function(element, effect) {
153 toggle: function(element, effect) {
151 element = $(element);
154 element = $(element);
152 effect = (effect || 'appear').toLowerCase();
155 effect = (effect || 'appear').toLowerCase();
153 var options = Object.extend({
156 var options = Object.extend({
154 queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
157 queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
155 }, arguments[2] || {});
158 }, arguments[2] || { });
156 Effect[element.visible() ?
159 Effect[element.visible() ?
157 Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
160 Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
158 }
161 }
159 };
162 };
160
163
161 var Effect2 = Effect; // deprecated
164 Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
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 };
194
165
195 /* ------------- core effects ------------- */
166 /* ------------- core effects ------------- */
196
167
197 Effect.ScopedQueue = Class.create();
168 Effect.ScopedQueue = Class.create(Enumerable, {
198 Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
199 initialize: function() {
169 initialize: function() {
200 this.effects = [];
170 this.effects = [];
201 this.interval = null;
171 this.interval = null;
202 },
172 },
203 _each: function(iterator) {
173 _each: function(iterator) {
204 this.effects._each(iterator);
174 this.effects._each(iterator);
205 },
175 },
206 add: function(effect) {
176 add: function(effect) {
207 var timestamp = new Date().getTime();
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 effect.options.queue : effect.options.queue.position;
180 effect.options.queue : effect.options.queue.position;
211
181
212 switch(position) {
182 switch(position) {
213 case 'front':
183 case 'front':
214 // move unstarted effects after this effect
184 // move unstarted effects after this effect
215 this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
185 this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
216 e.startOn += effect.finishOn;
186 e.startOn += effect.finishOn;
217 e.finishOn += effect.finishOn;
187 e.finishOn += effect.finishOn;
218 });
188 });
219 break;
189 break;
220 case 'with-last':
190 case 'with-last':
221 timestamp = this.effects.pluck('startOn').max() || timestamp;
191 timestamp = this.effects.pluck('startOn').max() || timestamp;
222 break;
192 break;
223 case 'end':
193 case 'end':
224 // start effect after last queued effect has finished
194 // start effect after last queued effect has finished
225 timestamp = this.effects.pluck('finishOn').max() || timestamp;
195 timestamp = this.effects.pluck('finishOn').max() || timestamp;
226 break;
196 break;
227 }
197 }
228
198
229 effect.startOn += timestamp;
199 effect.startOn += timestamp;
230 effect.finishOn += timestamp;
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 this.effects.push(effect);
203 this.effects.push(effect);
234
204
235 if(!this.interval)
205 if (!this.interval)
236 this.interval = setInterval(this.loop.bind(this), 40);
206 this.interval = setInterval(this.loop.bind(this), 15);
237 },
207 },
238 remove: function(effect) {
208 remove: function(effect) {
239 this.effects = this.effects.reject(function(e) { return e==effect });
209 this.effects = this.effects.reject(function(e) { return e==effect });
240 if(this.effects.length == 0) {
210 if (this.effects.length == 0) {
241 clearInterval(this.interval);
211 clearInterval(this.interval);
242 this.interval = null;
212 this.interval = null;
243 }
213 }
244 },
214 },
245 loop: function() {
215 loop: function() {
246 var timePos = new Date().getTime();
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 Effect.Queues = {
222 Effect.Queues = {
252 instances: $H(),
223 instances: $H(),
253 get: function(queueName) {
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])
227 return this.instances.get(queueName) ||
257 this.instances[queueName] = new Effect.ScopedQueue();
228 this.instances.set(queueName, new Effect.ScopedQueue());
258
259 return this.instances[queueName];
260 }
229 }
261 }
230 };
262 Effect.Queue = Effect.Queues.get('global');
231 Effect.Queue = Effect.Queues.get('global');
263
232
264 Effect.DefaultOptions = {
233 Effect.Base = Class.create({
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 = {
277 position: null,
234 position: null,
278 start: function(options) {
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 this.currentFrame = 0;
244 this.currentFrame = 0;
281 this.state = 'idle';
245 this.state = 'idle';
282 this.startOn = this.options.delay*1000;
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 this.event('beforeStart');
265 this.event('beforeStart');
285 if(!this.options.sync)
266 if (!this.options.sync)
286 Effect.Queues.get(typeof this.options.queue == 'string' ?
267 Effect.Queues.get(Object.isString(this.options.queue) ?
287 'global' : this.options.queue.scope).add(this);
268 'global' : this.options.queue.scope).add(this);
288 },
269 },
289 loop: function(timePos) {
270 loop: function(timePos) {
290 if(timePos >= this.startOn) {
271 if (timePos >= this.startOn) {
291 if(timePos >= this.finishOn) {
272 if (timePos >= this.finishOn) {
292 this.render(1.0);
273 this.render(1.0);
293 this.cancel();
274 this.cancel();
294 this.event('beforeFinish');
275 this.event('beforeFinish');
295 if(this.finish) this.finish();
276 if (this.finish) this.finish();
296 this.event('afterFinish');
277 this.event('afterFinish');
297 return;
278 return;
298 }
279 }
299 var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
280 var pos = (timePos - this.startOn) / this.totalTime,
300 var frame = Math.round(pos * this.options.fps * this.options.duration);
281 frame = (pos * this.totalFrames).round();
301 if(frame > this.currentFrame) {
282 if (frame > this.currentFrame) {
302 this.render(pos);
283 this.render(pos);
303 this.currentFrame = frame;
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 cancel: function() {
288 cancel: function() {
325 if(!this.options.sync)
289 if (!this.options.sync)
326 Effect.Queues.get(typeof this.options.queue == 'string' ?
290 Effect.Queues.get(Object.isString(this.options.queue) ?
327 'global' : this.options.queue.scope).remove(this);
291 'global' : this.options.queue.scope).remove(this);
328 this.state = 'finished';
292 this.state = 'finished';
329 },
293 },
330 event: function(eventName) {
294 event: function(eventName) {
331 if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
295 if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
332 if(this.options[eventName]) this.options[eventName](this);
296 if (this.options[eventName]) this.options[eventName](this);
333 },
297 },
334 inspect: function() {
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();
306 Effect.Parallel = Class.create(Effect.Base, {
340 Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
341 initialize: function(effects) {
307 initialize: function(effects) {
342 this.effects = effects || [];
308 this.effects = effects || [];
343 this.start(arguments[1]);
309 this.start(arguments[1]);
344 },
310 },
345 update: function(position) {
311 update: function(position) {
346 this.effects.invoke('render', position);
312 this.effects.invoke('render', position);
347 },
313 },
348 finish: function(position) {
314 finish: function(position) {
349 this.effects.each( function(effect) {
315 this.effects.each( function(effect) {
350 effect.render(1.0);
316 effect.render(1.0);
351 effect.cancel();
317 effect.cancel();
352 effect.event('beforeFinish');
318 effect.event('beforeFinish');
353 if(effect.finish) effect.finish(position);
319 if (effect.finish) effect.finish(position);
354 effect.event('afterFinish');
320 effect.event('afterFinish');
355 });
321 });
356 }
322 }
357 });
323 });
358
324
359 Effect.Event = Class.create();
325 Effect.Tween = Class.create(Effect.Base, {
360 Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), {
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 initialize: function() {
341 initialize: function() {
362 var options = Object.extend({
342 this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
363 duration: 0
364 }, arguments[0] || {});
365 this.start(options);
366 },
343 },
367 update: Prototype.emptyFunction
344 update: Prototype.emptyFunction
368 });
345 });
369
346
370 Effect.Opacity = Class.create();
347 Effect.Opacity = Class.create(Effect.Base, {
371 Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
372 initialize: function(element) {
348 initialize: function(element) {
373 this.element = $(element);
349 this.element = $(element);
374 if(!this.element) throw(Effect._elementDoesNotExistError);
350 if (!this.element) throw(Effect._elementDoesNotExistError);
375 // make this work on IE on elements without 'layout'
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 this.element.setStyle({zoom: 1});
353 this.element.setStyle({zoom: 1});
378 var options = Object.extend({
354 var options = Object.extend({
379 from: this.element.getOpacity() || 0.0,
355 from: this.element.getOpacity() || 0.0,
380 to: 1.0
356 to: 1.0
381 }, arguments[1] || {});
357 }, arguments[1] || { });
382 this.start(options);
358 this.start(options);
383 },
359 },
384 update: function(position) {
360 update: function(position) {
385 this.element.setOpacity(position);
361 this.element.setOpacity(position);
386 }
362 }
387 });
363 });
388
364
389 Effect.Move = Class.create();
365 Effect.Move = Class.create(Effect.Base, {
390 Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
391 initialize: function(element) {
366 initialize: function(element) {
392 this.element = $(element);
367 this.element = $(element);
393 if(!this.element) throw(Effect._elementDoesNotExistError);
368 if (!this.element) throw(Effect._elementDoesNotExistError);
394 var options = Object.extend({
369 var options = Object.extend({
395 x: 0,
370 x: 0,
396 y: 0,
371 y: 0,
397 mode: 'relative'
372 mode: 'relative'
398 }, arguments[1] || {});
373 }, arguments[1] || { });
399 this.start(options);
374 this.start(options);
400 },
375 },
401 setup: function() {
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 this.element.makePositioned();
377 this.element.makePositioned();
407 this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
378 this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
408 this.originalTop = parseFloat(this.element.getStyle('top') || '0');
379 this.originalTop = parseFloat(this.element.getStyle('top') || '0');
409 if(this.options.mode == 'absolute') {
380 if (this.options.mode == 'absolute') {
410 // absolute movement, so we need to calc deltaX and deltaY
411 this.options.x = this.options.x - this.originalLeft;
381 this.options.x = this.options.x - this.originalLeft;
412 this.options.y = this.options.y - this.originalTop;
382 this.options.y = this.options.y - this.originalTop;
413 }
383 }
414 },
384 },
415 update: function(position) {
385 update: function(position) {
416 this.element.setStyle({
386 this.element.setStyle({
417 left: Math.round(this.options.x * position + this.originalLeft) + 'px',
387 left: (this.options.x * position + this.originalLeft).round() + 'px',
418 top: Math.round(this.options.y * position + this.originalTop) + 'px'
388 top: (this.options.y * position + this.originalTop).round() + 'px'
419 });
389 });
420 }
390 }
421 });
391 });
422
392
423 // for backwards compatibility
393 // for backwards compatibility
424 Effect.MoveBy = function(element, toTop, toLeft) {
394 Effect.MoveBy = function(element, toTop, toLeft) {
425 return new Effect.Move(element,
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();
399 Effect.Scale = Class.create(Effect.Base, {
430 Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
431 initialize: function(element, percent) {
400 initialize: function(element, percent) {
432 this.element = $(element);
401 this.element = $(element);
433 if(!this.element) throw(Effect._elementDoesNotExistError);
402 if (!this.element) throw(Effect._elementDoesNotExistError);
434 var options = Object.extend({
403 var options = Object.extend({
435 scaleX: true,
404 scaleX: true,
436 scaleY: true,
405 scaleY: true,
437 scaleContent: true,
406 scaleContent: true,
438 scaleFromCenter: false,
407 scaleFromCenter: false,
439 scaleMode: 'box', // 'box' or 'contents' or {} with provided values
408 scaleMode: 'box', // 'box' or 'contents' or { } with provided values
440 scaleFrom: 100.0,
409 scaleFrom: 100.0,
441 scaleTo: percent
410 scaleTo: percent
442 }, arguments[2] || {});
411 }, arguments[2] || { });
443 this.start(options);
412 this.start(options);
444 },
413 },
445 setup: function() {
414 setup: function() {
446 this.restoreAfterFinish = this.options.restoreAfterFinish || false;
415 this.restoreAfterFinish = this.options.restoreAfterFinish || false;
447 this.elementPositioning = this.element.getStyle('position');
416 this.elementPositioning = this.element.getStyle('position');
448
417
449 this.originalStyle = {};
418 this.originalStyle = { };
450 ['top','left','width','height','fontSize'].each( function(k) {
419 ['top','left','width','height','fontSize'].each( function(k) {
451 this.originalStyle[k] = this.element.style[k];
420 this.originalStyle[k] = this.element.style[k];
452 }.bind(this));
421 }.bind(this));
453
422
454 this.originalTop = this.element.offsetTop;
423 this.originalTop = this.element.offsetTop;
455 this.originalLeft = this.element.offsetLeft;
424 this.originalLeft = this.element.offsetLeft;
456
425
457 var fontSize = this.element.getStyle('font-size') || '100%';
426 var fontSize = this.element.getStyle('font-size') || '100%';
458 ['em','px','%','pt'].each( function(fontSizeType) {
427 ['em','px','%','pt'].each( function(fontSizeType) {
459 if(fontSize.indexOf(fontSizeType)>0) {
428 if (fontSize.indexOf(fontSizeType)>0) {
460 this.fontSize = parseFloat(fontSize);
429 this.fontSize = parseFloat(fontSize);
461 this.fontSizeType = fontSizeType;
430 this.fontSizeType = fontSizeType;
462 }
431 }
463 }.bind(this));
432 }.bind(this));
464
433
465 this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
434 this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
466
435
467 this.dims = null;
436 this.dims = null;
468 if(this.options.scaleMode=='box')
437 if (this.options.scaleMode=='box')
469 this.dims = [this.element.offsetHeight, this.element.offsetWidth];
438 this.dims = [this.element.offsetHeight, this.element.offsetWidth];
470 if(/^content/.test(this.options.scaleMode))
439 if (/^content/.test(this.options.scaleMode))
471 this.dims = [this.element.scrollHeight, this.element.scrollWidth];
440 this.dims = [this.element.scrollHeight, this.element.scrollWidth];
472 if(!this.dims)
441 if (!this.dims)
473 this.dims = [this.options.scaleMode.originalHeight,
442 this.dims = [this.options.scaleMode.originalHeight,
474 this.options.scaleMode.originalWidth];
443 this.options.scaleMode.originalWidth];
475 },
444 },
476 update: function(position) {
445 update: function(position) {
477 var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
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 this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
448 this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
480 this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
449 this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
481 },
450 },
482 finish: function(position) {
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 setDimensions: function(height, width) {
454 setDimensions: function(height, width) {
486 var d = {};
455 var d = { };
487 if(this.options.scaleX) d.width = Math.round(width) + 'px';
456 if (this.options.scaleX) d.width = width.round() + 'px';
488 if(this.options.scaleY) d.height = Math.round(height) + 'px';
457 if (this.options.scaleY) d.height = height.round() + 'px';
489 if(this.options.scaleFromCenter) {
458 if (this.options.scaleFromCenter) {
490 var topd = (height - this.dims[0])/2;
459 var topd = (height - this.dims[0])/2;
491 var leftd = (width - this.dims[1])/2;
460 var leftd = (width - this.dims[1])/2;
492 if(this.elementPositioning == 'absolute') {
461 if (this.elementPositioning == 'absolute') {
493 if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
462 if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
494 if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
463 if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
495 } else {
464 } else {
496 if(this.options.scaleY) d.top = -topd + 'px';
465 if (this.options.scaleY) d.top = -topd + 'px';
497 if(this.options.scaleX) d.left = -leftd + 'px';
466 if (this.options.scaleX) d.left = -leftd + 'px';
498 }
467 }
499 }
468 }
500 this.element.setStyle(d);
469 this.element.setStyle(d);
501 }
470 }
502 });
471 });
503
472
504 Effect.Highlight = Class.create();
473 Effect.Highlight = Class.create(Effect.Base, {
505 Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
506 initialize: function(element) {
474 initialize: function(element) {
507 this.element = $(element);
475 this.element = $(element);
508 if(!this.element) throw(Effect._elementDoesNotExistError);
476 if (!this.element) throw(Effect._elementDoesNotExistError);
509 var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
477 var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
510 this.start(options);
478 this.start(options);
511 },
479 },
512 setup: function() {
480 setup: function() {
513 // Prevent executing on elements not in the layout flow
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 // Disable background image during the effect
483 // Disable background image during the effect
516 this.oldStyle = {
484 this.oldStyle = { };
517 backgroundImage: this.element.getStyle('background-image') };
485 if (!this.options.keepBackgroundImage) {
518 this.element.setStyle({backgroundImage: 'none'});
486 this.oldStyle.backgroundImage = this.element.getStyle('background-image');
519 if(!this.options.endcolor)
487 this.element.setStyle({backgroundImage: 'none'});
488 }
489 if (!this.options.endcolor)
520 this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
490 this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
521 if(!this.options.restorecolor)
491 if (!this.options.restorecolor)
522 this.options.restorecolor = this.element.getStyle('background-color');
492 this.options.restorecolor = this.element.getStyle('background-color');
523 // init color calculations
493 // init color calculations
524 this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
494 this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
525 this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
495 this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
526 },
496 },
527 update: function(position) {
497 update: function(position) {
528 this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
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 finish: function() {
501 finish: function() {
532 this.element.setStyle(Object.extend(this.oldStyle, {
502 this.element.setStyle(Object.extend(this.oldStyle, {
533 backgroundColor: this.options.restorecolor
503 backgroundColor: this.options.restorecolor
534 }));
504 }));
535 }
505 }
536 });
506 });
537
507
538 Effect.ScrollTo = Class.create();
508 Effect.ScrollTo = function(element) {
539 Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
509 var options = arguments[1] || { },
540 initialize: function(element) {
510 scrollOffsets = document.viewport.getScrollOffsets(),
541 this.element = $(element);
511 elementOffsets = $(element).cumulativeOffset(),
542 this.start(arguments[1] || {});
512 max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();
543 },
513
544 setup: function() {
514 if (options.offset) elementOffsets[1] += options.offset;
545 Position.prepare();
515
546 var offsets = Position.cumulativeOffset(this.element);
516 return new Effect.Tween(null,
547 if(this.options.offset) offsets[1] += this.options.offset;
517 scrollOffsets.top,
548 var max = window.innerHeight ?
518 elementOffsets[1] > max ? max : elementOffsets[1],
549 window.height - window.innerHeight :
519 options,
550 document.body.scrollHeight -
520 function(p){ scrollTo(scrollOffsets.left, p.round()) }
551 (document.documentElement.clientHeight ?
521 );
552 document.documentElement.clientHeight : document.body.clientHeight);
522 };
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 });
562
523
563 /* ------------- combination effects ------------- */
524 /* ------------- combination effects ------------- */
564
525
565 Effect.Fade = function(element) {
526 Effect.Fade = function(element) {
566 element = $(element);
527 element = $(element);
567 var oldOpacity = element.getInlineOpacity();
528 var oldOpacity = element.getInlineOpacity();
568 var options = Object.extend({
529 var options = Object.extend({
569 from: element.getOpacity() || 1.0,
530 from: element.getOpacity() || 1.0,
570 to: 0.0,
531 to: 0.0,
571 afterFinishInternal: function(effect) {
532 afterFinishInternal: function(effect) {
572 if(effect.options.to!=0) return;
533 if (effect.options.to!=0) return;
573 effect.element.hide().setStyle({opacity: oldOpacity});
534 effect.element.hide().setStyle({opacity: oldOpacity});
574 }}, arguments[1] || {});
535 }
536 }, arguments[1] || { });
575 return new Effect.Opacity(element,options);
537 return new Effect.Opacity(element,options);
576 }
538 };
577
539
578 Effect.Appear = function(element) {
540 Effect.Appear = function(element) {
579 element = $(element);
541 element = $(element);
580 var options = Object.extend({
542 var options = Object.extend({
581 from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
543 from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
582 to: 1.0,
544 to: 1.0,
583 // force Safari to render floated elements properly
545 // force Safari to render floated elements properly
584 afterFinishInternal: function(effect) {
546 afterFinishInternal: function(effect) {
585 effect.element.forceRerendering();
547 effect.element.forceRerendering();
586 },
548 },
587 beforeSetup: function(effect) {
549 beforeSetup: function(effect) {
588 effect.element.setOpacity(effect.options.from).show();
550 effect.element.setOpacity(effect.options.from).show();
589 }}, arguments[1] || {});
551 }}, arguments[1] || { });
590 return new Effect.Opacity(element,options);
552 return new Effect.Opacity(element,options);
591 }
553 };
592
554
593 Effect.Puff = function(element) {
555 Effect.Puff = function(element) {
594 element = $(element);
556 element = $(element);
595 var oldStyle = {
557 var oldStyle = {
596 opacity: element.getInlineOpacity(),
558 opacity: element.getInlineOpacity(),
597 position: element.getStyle('position'),
559 position: element.getStyle('position'),
598 top: element.style.top,
560 top: element.style.top,
599 left: element.style.left,
561 left: element.style.left,
600 width: element.style.width,
562 width: element.style.width,
601 height: element.style.height
563 height: element.style.height
602 };
564 };
603 return new Effect.Parallel(
565 return new Effect.Parallel(
604 [ new Effect.Scale(element, 200,
566 [ new Effect.Scale(element, 200,
605 { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
567 { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
606 new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
568 new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
607 Object.extend({ duration: 1.0,
569 Object.extend({ duration: 1.0,
608 beforeSetupInternal: function(effect) {
570 beforeSetupInternal: function(effect) {
609 Position.absolutize(effect.effects[0].element)
571 Position.absolutize(effect.effects[0].element)
610 },
572 },
611 afterFinishInternal: function(effect) {
573 afterFinishInternal: function(effect) {
612 effect.effects[0].element.hide().setStyle(oldStyle); }
574 effect.effects[0].element.hide().setStyle(oldStyle); }
613 }, arguments[1] || {})
575 }, arguments[1] || { })
614 );
576 );
615 }
577 };
616
578
617 Effect.BlindUp = function(element) {
579 Effect.BlindUp = function(element) {
618 element = $(element);
580 element = $(element);
619 element.makeClipping();
581 element.makeClipping();
620 return new Effect.Scale(element, 0,
582 return new Effect.Scale(element, 0,
621 Object.extend({ scaleContent: false,
583 Object.extend({ scaleContent: false,
622 scaleX: false,
584 scaleX: false,
623 restoreAfterFinish: true,
585 restoreAfterFinish: true,
624 afterFinishInternal: function(effect) {
586 afterFinishInternal: function(effect) {
625 effect.element.hide().undoClipping();
587 effect.element.hide().undoClipping();
626 }
588 }
627 }, arguments[1] || {})
589 }, arguments[1] || { })
628 );
590 );
629 }
591 };
630
592
631 Effect.BlindDown = function(element) {
593 Effect.BlindDown = function(element) {
632 element = $(element);
594 element = $(element);
633 var elementDimensions = element.getDimensions();
595 var elementDimensions = element.getDimensions();
634 return new Effect.Scale(element, 100, Object.extend({
596 return new Effect.Scale(element, 100, Object.extend({
635 scaleContent: false,
597 scaleContent: false,
636 scaleX: false,
598 scaleX: false,
637 scaleFrom: 0,
599 scaleFrom: 0,
638 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
600 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
639 restoreAfterFinish: true,
601 restoreAfterFinish: true,
640 afterSetup: function(effect) {
602 afterSetup: function(effect) {
641 effect.element.makeClipping().setStyle({height: '0px'}).show();
603 effect.element.makeClipping().setStyle({height: '0px'}).show();
642 },
604 },
643 afterFinishInternal: function(effect) {
605 afterFinishInternal: function(effect) {
644 effect.element.undoClipping();
606 effect.element.undoClipping();
645 }
607 }
646 }, arguments[1] || {}));
608 }, arguments[1] || { }));
647 }
609 };
648
610
649 Effect.SwitchOff = function(element) {
611 Effect.SwitchOff = function(element) {
650 element = $(element);
612 element = $(element);
651 var oldOpacity = element.getInlineOpacity();
613 var oldOpacity = element.getInlineOpacity();
652 return new Effect.Appear(element, Object.extend({
614 return new Effect.Appear(element, Object.extend({
653 duration: 0.4,
615 duration: 0.4,
654 from: 0,
616 from: 0,
655 transition: Effect.Transitions.flicker,
617 transition: Effect.Transitions.flicker,
656 afterFinishInternal: function(effect) {
618 afterFinishInternal: function(effect) {
657 new Effect.Scale(effect.element, 1, {
619 new Effect.Scale(effect.element, 1, {
658 duration: 0.3, scaleFromCenter: true,
620 duration: 0.3, scaleFromCenter: true,
659 scaleX: false, scaleContent: false, restoreAfterFinish: true,
621 scaleX: false, scaleContent: false, restoreAfterFinish: true,
660 beforeSetup: function(effect) {
622 beforeSetup: function(effect) {
661 effect.element.makePositioned().makeClipping();
623 effect.element.makePositioned().makeClipping();
662 },
624 },
663 afterFinishInternal: function(effect) {
625 afterFinishInternal: function(effect) {
664 effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
626 effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
665 }
627 }
666 })
628 })
667 }
629 }
668 }, arguments[1] || {}));
630 }, arguments[1] || { }));
669 }
631 };
670
632
671 Effect.DropOut = function(element) {
633 Effect.DropOut = function(element) {
672 element = $(element);
634 element = $(element);
673 var oldStyle = {
635 var oldStyle = {
674 top: element.getStyle('top'),
636 top: element.getStyle('top'),
675 left: element.getStyle('left'),
637 left: element.getStyle('left'),
676 opacity: element.getInlineOpacity() };
638 opacity: element.getInlineOpacity() };
677 return new Effect.Parallel(
639 return new Effect.Parallel(
678 [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
640 [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
679 new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
641 new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
680 Object.extend(
642 Object.extend(
681 { duration: 0.5,
643 { duration: 0.5,
682 beforeSetup: function(effect) {
644 beforeSetup: function(effect) {
683 effect.effects[0].element.makePositioned();
645 effect.effects[0].element.makePositioned();
684 },
646 },
685 afterFinishInternal: function(effect) {
647 afterFinishInternal: function(effect) {
686 effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
648 effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
687 }
649 }
688 }, arguments[1] || {}));
650 }, arguments[1] || { }));
689 }
651 };
690
652
691 Effect.Shake = function(element) {
653 Effect.Shake = function(element) {
692 element = $(element);
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 var oldStyle = {
661 var oldStyle = {
694 top: element.getStyle('top'),
662 top: element.getStyle('top'),
695 left: element.getStyle('left') };
663 left: element.getStyle('left') };
696 return new Effect.Move(element,
664 return new Effect.Move(element,
697 { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
665 { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) {
698 new Effect.Move(effect.element,
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 new Effect.Move(effect.element,
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 new Effect.Move(effect.element,
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 new Effect.Move(effect.element,
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 new Effect.Move(effect.element,
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 effect.element.undoPositioned().setStyle(oldStyle);
676 effect.element.undoPositioned().setStyle(oldStyle);
709 }}) }}) }}) }}) }}) }});
677 }}) }}) }}) }}) }}) }});
710 }
678 };
711
679
712 Effect.SlideDown = function(element) {
680 Effect.SlideDown = function(element) {
713 element = $(element).cleanWhitespace();
681 element = $(element).cleanWhitespace();
714 // SlideDown need to have the content of the element wrapped in a container element with fixed height!
682 // SlideDown need to have the content of the element wrapped in a container element with fixed height!
715 var oldInnerBottom = element.down().getStyle('bottom');
683 var oldInnerBottom = element.down().getStyle('bottom');
716 var elementDimensions = element.getDimensions();
684 var elementDimensions = element.getDimensions();
717 return new Effect.Scale(element, 100, Object.extend({
685 return new Effect.Scale(element, 100, Object.extend({
718 scaleContent: false,
686 scaleContent: false,
719 scaleX: false,
687 scaleX: false,
720 scaleFrom: window.opera ? 0 : 1,
688 scaleFrom: window.opera ? 0 : 1,
721 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
689 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
722 restoreAfterFinish: true,
690 restoreAfterFinish: true,
723 afterSetup: function(effect) {
691 afterSetup: function(effect) {
724 effect.element.makePositioned();
692 effect.element.makePositioned();
725 effect.element.down().makePositioned();
693 effect.element.down().makePositioned();
726 if(window.opera) effect.element.setStyle({top: ''});
694 if (window.opera) effect.element.setStyle({top: ''});
727 effect.element.makeClipping().setStyle({height: '0px'}).show();
695 effect.element.makeClipping().setStyle({height: '0px'}).show();
728 },
696 },
729 afterUpdateInternal: function(effect) {
697 afterUpdateInternal: function(effect) {
730 effect.element.down().setStyle({bottom:
698 effect.element.down().setStyle({bottom:
731 (effect.dims[0] - effect.element.clientHeight) + 'px' });
699 (effect.dims[0] - effect.element.clientHeight) + 'px' });
732 },
700 },
733 afterFinishInternal: function(effect) {
701 afterFinishInternal: function(effect) {
734 effect.element.undoClipping().undoPositioned();
702 effect.element.undoClipping().undoPositioned();
735 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
703 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
736 }, arguments[1] || {})
704 }, arguments[1] || { })
737 );
705 );
738 }
706 };
739
707
740 Effect.SlideUp = function(element) {
708 Effect.SlideUp = function(element) {
741 element = $(element).cleanWhitespace();
709 element = $(element).cleanWhitespace();
742 var oldInnerBottom = element.down().getStyle('bottom');
710 var oldInnerBottom = element.down().getStyle('bottom');
711 var elementDimensions = element.getDimensions();
743 return new Effect.Scale(element, window.opera ? 0 : 1,
712 return new Effect.Scale(element, window.opera ? 0 : 1,
744 Object.extend({ scaleContent: false,
713 Object.extend({ scaleContent: false,
745 scaleX: false,
714 scaleX: false,
746 scaleMode: 'box',
715 scaleMode: 'box',
747 scaleFrom: 100,
716 scaleFrom: 100,
717 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
748 restoreAfterFinish: true,
718 restoreAfterFinish: true,
749 beforeStartInternal: function(effect) {
719 afterSetup: function(effect) {
750 effect.element.makePositioned();
720 effect.element.makePositioned();
751 effect.element.down().makePositioned();
721 effect.element.down().makePositioned();
752 if(window.opera) effect.element.setStyle({top: ''});
722 if (window.opera) effect.element.setStyle({top: ''});
753 effect.element.makeClipping().show();
723 effect.element.makeClipping().show();
754 },
724 },
755 afterUpdateInternal: function(effect) {
725 afterUpdateInternal: function(effect) {
756 effect.element.down().setStyle({bottom:
726 effect.element.down().setStyle({bottom:
757 (effect.dims[0] - effect.element.clientHeight) + 'px' });
727 (effect.dims[0] - effect.element.clientHeight) + 'px' });
758 },
728 },
759 afterFinishInternal: function(effect) {
729 afterFinishInternal: function(effect) {
760 effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom});
730 effect.element.hide().undoClipping().undoPositioned();
761 effect.element.down().undoPositioned();
731 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
762 }
732 }
763 }, arguments[1] || {})
733 }, arguments[1] || { })
764 );
734 );
765 }
735 };
766
736
767 // Bug in opera makes the TD containing this element expand for a instance after finish
737 // Bug in opera makes the TD containing this element expand for a instance after finish
768 Effect.Squish = function(element) {
738 Effect.Squish = function(element) {
769 return new Effect.Scale(element, window.opera ? 1 : 0, {
739 return new Effect.Scale(element, window.opera ? 1 : 0, {
770 restoreAfterFinish: true,
740 restoreAfterFinish: true,
771 beforeSetup: function(effect) {
741 beforeSetup: function(effect) {
772 effect.element.makeClipping();
742 effect.element.makeClipping();
773 },
743 },
774 afterFinishInternal: function(effect) {
744 afterFinishInternal: function(effect) {
775 effect.element.hide().undoClipping();
745 effect.element.hide().undoClipping();
776 }
746 }
777 });
747 });
778 }
748 };
779
749
780 Effect.Grow = function(element) {
750 Effect.Grow = function(element) {
781 element = $(element);
751 element = $(element);
782 var options = Object.extend({
752 var options = Object.extend({
783 direction: 'center',
753 direction: 'center',
784 moveTransition: Effect.Transitions.sinoidal,
754 moveTransition: Effect.Transitions.sinoidal,
785 scaleTransition: Effect.Transitions.sinoidal,
755 scaleTransition: Effect.Transitions.sinoidal,
786 opacityTransition: Effect.Transitions.full
756 opacityTransition: Effect.Transitions.full
787 }, arguments[1] || {});
757 }, arguments[1] || { });
788 var oldStyle = {
758 var oldStyle = {
789 top: element.style.top,
759 top: element.style.top,
790 left: element.style.left,
760 left: element.style.left,
791 height: element.style.height,
761 height: element.style.height,
792 width: element.style.width,
762 width: element.style.width,
793 opacity: element.getInlineOpacity() };
763 opacity: element.getInlineOpacity() };
794
764
795 var dims = element.getDimensions();
765 var dims = element.getDimensions();
796 var initialMoveX, initialMoveY;
766 var initialMoveX, initialMoveY;
797 var moveX, moveY;
767 var moveX, moveY;
798
768
799 switch (options.direction) {
769 switch (options.direction) {
800 case 'top-left':
770 case 'top-left':
801 initialMoveX = initialMoveY = moveX = moveY = 0;
771 initialMoveX = initialMoveY = moveX = moveY = 0;
802 break;
772 break;
803 case 'top-right':
773 case 'top-right':
804 initialMoveX = dims.width;
774 initialMoveX = dims.width;
805 initialMoveY = moveY = 0;
775 initialMoveY = moveY = 0;
806 moveX = -dims.width;
776 moveX = -dims.width;
807 break;
777 break;
808 case 'bottom-left':
778 case 'bottom-left':
809 initialMoveX = moveX = 0;
779 initialMoveX = moveX = 0;
810 initialMoveY = dims.height;
780 initialMoveY = dims.height;
811 moveY = -dims.height;
781 moveY = -dims.height;
812 break;
782 break;
813 case 'bottom-right':
783 case 'bottom-right':
814 initialMoveX = dims.width;
784 initialMoveX = dims.width;
815 initialMoveY = dims.height;
785 initialMoveY = dims.height;
816 moveX = -dims.width;
786 moveX = -dims.width;
817 moveY = -dims.height;
787 moveY = -dims.height;
818 break;
788 break;
819 case 'center':
789 case 'center':
820 initialMoveX = dims.width / 2;
790 initialMoveX = dims.width / 2;
821 initialMoveY = dims.height / 2;
791 initialMoveY = dims.height / 2;
822 moveX = -dims.width / 2;
792 moveX = -dims.width / 2;
823 moveY = -dims.height / 2;
793 moveY = -dims.height / 2;
824 break;
794 break;
825 }
795 }
826
796
827 return new Effect.Move(element, {
797 return new Effect.Move(element, {
828 x: initialMoveX,
798 x: initialMoveX,
829 y: initialMoveY,
799 y: initialMoveY,
830 duration: 0.01,
800 duration: 0.01,
831 beforeSetup: function(effect) {
801 beforeSetup: function(effect) {
832 effect.element.hide().makeClipping().makePositioned();
802 effect.element.hide().makeClipping().makePositioned();
833 },
803 },
834 afterFinishInternal: function(effect) {
804 afterFinishInternal: function(effect) {
835 new Effect.Parallel(
805 new Effect.Parallel(
836 [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
806 [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
837 new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
807 new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
838 new Effect.Scale(effect.element, 100, {
808 new Effect.Scale(effect.element, 100, {
839 scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
809 scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
840 sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
810 sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
841 ], Object.extend({
811 ], Object.extend({
842 beforeSetup: function(effect) {
812 beforeSetup: function(effect) {
843 effect.effects[0].element.setStyle({height: '0px'}).show();
813 effect.effects[0].element.setStyle({height: '0px'}).show();
844 },
814 },
845 afterFinishInternal: function(effect) {
815 afterFinishInternal: function(effect) {
846 effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
816 effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
847 }
817 }
848 }, options)
818 }, options)
849 )
819 )
850 }
820 }
851 });
821 });
852 }
822 };
853
823
854 Effect.Shrink = function(element) {
824 Effect.Shrink = function(element) {
855 element = $(element);
825 element = $(element);
856 var options = Object.extend({
826 var options = Object.extend({
857 direction: 'center',
827 direction: 'center',
858 moveTransition: Effect.Transitions.sinoidal,
828 moveTransition: Effect.Transitions.sinoidal,
859 scaleTransition: Effect.Transitions.sinoidal,
829 scaleTransition: Effect.Transitions.sinoidal,
860 opacityTransition: Effect.Transitions.none
830 opacityTransition: Effect.Transitions.none
861 }, arguments[1] || {});
831 }, arguments[1] || { });
862 var oldStyle = {
832 var oldStyle = {
863 top: element.style.top,
833 top: element.style.top,
864 left: element.style.left,
834 left: element.style.left,
865 height: element.style.height,
835 height: element.style.height,
866 width: element.style.width,
836 width: element.style.width,
867 opacity: element.getInlineOpacity() };
837 opacity: element.getInlineOpacity() };
868
838
869 var dims = element.getDimensions();
839 var dims = element.getDimensions();
870 var moveX, moveY;
840 var moveX, moveY;
871
841
872 switch (options.direction) {
842 switch (options.direction) {
873 case 'top-left':
843 case 'top-left':
874 moveX = moveY = 0;
844 moveX = moveY = 0;
875 break;
845 break;
876 case 'top-right':
846 case 'top-right':
877 moveX = dims.width;
847 moveX = dims.width;
878 moveY = 0;
848 moveY = 0;
879 break;
849 break;
880 case 'bottom-left':
850 case 'bottom-left':
881 moveX = 0;
851 moveX = 0;
882 moveY = dims.height;
852 moveY = dims.height;
883 break;
853 break;
884 case 'bottom-right':
854 case 'bottom-right':
885 moveX = dims.width;
855 moveX = dims.width;
886 moveY = dims.height;
856 moveY = dims.height;
887 break;
857 break;
888 case 'center':
858 case 'center':
889 moveX = dims.width / 2;
859 moveX = dims.width / 2;
890 moveY = dims.height / 2;
860 moveY = dims.height / 2;
891 break;
861 break;
892 }
862 }
893
863
894 return new Effect.Parallel(
864 return new Effect.Parallel(
895 [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
865 [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
896 new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
866 new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
897 new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
867 new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
898 ], Object.extend({
868 ], Object.extend({
899 beforeStartInternal: function(effect) {
869 beforeStartInternal: function(effect) {
900 effect.effects[0].element.makePositioned().makeClipping();
870 effect.effects[0].element.makePositioned().makeClipping();
901 },
871 },
902 afterFinishInternal: function(effect) {
872 afterFinishInternal: function(effect) {
903 effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
873 effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
904 }, options)
874 }, options)
905 );
875 );
906 }
876 };
907
877
908 Effect.Pulsate = function(element) {
878 Effect.Pulsate = function(element) {
909 element = $(element);
879 element = $(element);
910 var options = arguments[1] || {};
880 var options = arguments[1] || { };
911 var oldOpacity = element.getInlineOpacity();
881 var oldOpacity = element.getInlineOpacity();
912 var transition = options.transition || Effect.Transitions.sinoidal;
882 var transition = options.transition || Effect.Transitions.sinoidal;
913 var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
883 var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
914 reverser.bind(transition);
884 reverser.bind(transition);
915 return new Effect.Opacity(element,
885 return new Effect.Opacity(element,
916 Object.extend(Object.extend({ duration: 2.0, from: 0,
886 Object.extend(Object.extend({ duration: 2.0, from: 0,
917 afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
887 afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
918 }, options), {transition: reverser}));
888 }, options), {transition: reverser}));
919 }
889 };
920
890
921 Effect.Fold = function(element) {
891 Effect.Fold = function(element) {
922 element = $(element);
892 element = $(element);
923 var oldStyle = {
893 var oldStyle = {
924 top: element.style.top,
894 top: element.style.top,
925 left: element.style.left,
895 left: element.style.left,
926 width: element.style.width,
896 width: element.style.width,
927 height: element.style.height };
897 height: element.style.height };
928 element.makeClipping();
898 element.makeClipping();
929 return new Effect.Scale(element, 5, Object.extend({
899 return new Effect.Scale(element, 5, Object.extend({
930 scaleContent: false,
900 scaleContent: false,
931 scaleX: false,
901 scaleX: false,
932 afterFinishInternal: function(effect) {
902 afterFinishInternal: function(effect) {
933 new Effect.Scale(element, 1, {
903 new Effect.Scale(element, 1, {
934 scaleContent: false,
904 scaleContent: false,
935 scaleY: false,
905 scaleY: false,
936 afterFinishInternal: function(effect) {
906 afterFinishInternal: function(effect) {
937 effect.element.hide().undoClipping().setStyle(oldStyle);
907 effect.element.hide().undoClipping().setStyle(oldStyle);
938 } });
908 } });
939 }}, arguments[1] || {}));
909 }}, arguments[1] || { }));
940 };
910 };
941
911
942 Effect.Morph = Class.create();
912 Effect.Morph = Class.create(Effect.Base, {
943 Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
944 initialize: function(element) {
913 initialize: function(element) {
945 this.element = $(element);
914 this.element = $(element);
946 if(!this.element) throw(Effect._elementDoesNotExistError);
915 if (!this.element) throw(Effect._elementDoesNotExistError);
947 var options = Object.extend({
916 var options = Object.extend({
948 style: ''
917 style: { }
949 }, arguments[1] || {});
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 this.start(options);
940 this.start(options);
951 },
941 },
942
952 setup: function(){
943 setup: function(){
953 function parseColor(color){
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 color = color.parseColor();
946 color = color.parseColor();
956 return $R(0,2).map(function(i){
947 return $R(0,2).map(function(i){
957 return parseInt( color.slice(i*2+1,i*2+3), 16 )
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){
951 this.transforms = this.style.map(function(pair){
961 var originalValue = this.element.getStyle(property[0]);
952 var property = pair[0], value = pair[1], unit = null;
962 return $H({
953
963 style: property[0],
954 if (value.parseColor('#zzzzzz') != '#zzzzzz') {
964 originalValue: property[1].unit=='color' ?
955 value = value.parseColor();
965 parseColor(originalValue) : parseFloat(originalValue || 0),
956 unit = 'color';
966 targetValue: property[1].unit=='color' ?
957 } else if (property == 'opacity') {
967 parseColor(property[1].value) : property[1].value,
958 value = parseFloat(value);
968 unit: property[1].unit
959 if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
969 });
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 }.bind(this)).reject(function(transform){
974 }.bind(this)).reject(function(transform){
971 return (
975 return (
972 (transform.originalValue == transform.targetValue) ||
976 (transform.originalValue == transform.targetValue) ||
973 (
977 (
974 transform.unit != 'color' &&
978 transform.unit != 'color' &&
975 (isNaN(transform.originalValue) || isNaN(transform.targetValue))
979 (isNaN(transform.originalValue) || isNaN(transform.targetValue))
976 )
980 )
977 )
981 )
978 });
982 });
979 },
983 },
980 update: function(position) {
984 update: function(position) {
981 var style = $H(), value = null;
985 var style = { }, transform, i = this.transforms.length;
982 this.transforms.each(function(transform){
986 while(i--)
983 value = transform.unit=='color' ?
987 style[(transform = this.transforms[i]).style] =
984 $R(0,2).inject('#',function(m,v,i){
988 transform.unit=='color' ? '#'+
985 return m+(Math.round(transform.originalValue[i]+
989 (Math.round(transform.originalValue[0]+
986 (transform.targetValue[i] - transform.originalValue[i])*position)).toColorPart() }) :
990 (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
987 transform.originalValue + Math.round(
991 (Math.round(transform.originalValue[1]+
988 ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
992 (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
989 style[transform.style] = value;
993 (Math.round(transform.originalValue[2]+
990 });
994 (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
991 this.element.setStyle(style);
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();
1002 Effect.Transform = Class.create({
996 Object.extend(Effect.Transform.prototype, {
997 initialize: function(tracks){
1003 initialize: function(tracks){
998 this.tracks = [];
1004 this.tracks = [];
999 this.options = arguments[1] || {};
1005 this.options = arguments[1] || { };
1000 this.addTracks(tracks);
1006 this.addTracks(tracks);
1001 },
1007 },
1002 addTracks: function(tracks){
1008 addTracks: function(tracks){
1003 tracks.each(function(track){
1009 tracks.each(function(track){
1004 var data = $H(track).values().first();
1010 track = $H(track);
1011 var data = track.values().first();
1005 this.tracks.push($H({
1012 this.tracks.push($H({
1006 ids: $H(track).keys().first(),
1013 ids: track.keys().first(),
1007 effect: Effect.Morph,
1014 effect: Effect.Morph,
1008 options: { style: data }
1015 options: { style: data }
1009 }));
1016 }));
1010 }.bind(this));
1017 }.bind(this));
1011 return this;
1018 return this;
1012 },
1019 },
1013 play: function(){
1020 play: function(){
1014 return new Effect.Parallel(
1021 return new Effect.Parallel(
1015 this.tracks.map(function(track){
1022 this.tracks.map(function(track){
1016 var elements = [$(track.ids) || $$(track.ids)].flatten();
1023 var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
1017 return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
1024 var elements = [$(ids) || $$(ids)].flatten();
1025 return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
1018 }).flatten(),
1026 }).flatten(),
1019 this.options
1027 this.options
1020 );
1028 );
1021 }
1029 }
1022 });
1030 });
1023
1031
1024 Element.CSS_PROPERTIES = ['azimuth', 'backgroundAttachment', 'backgroundColor', 'backgroundImage',
1032 Element.CSS_PROPERTIES = $w(
1025 'backgroundPosition', 'backgroundRepeat', 'borderBottomColor', 'borderBottomStyle',
1033 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
1026 'borderBottomWidth', 'borderCollapse', 'borderLeftColor', 'borderLeftStyle', 'borderLeftWidth',
1034 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
1027 'borderRightColor', 'borderRightStyle', 'borderRightWidth', 'borderSpacing', 'borderTopColor',
1035 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
1028 'borderTopStyle', 'borderTopWidth', 'bottom', 'captionSide', 'clear', 'clip', 'color', 'content',
1036 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
1029 'counterIncrement', 'counterReset', 'cssFloat', 'cueAfter', 'cueBefore', 'cursor', 'direction',
1037 'fontSize fontWeight height left letterSpacing lineHeight ' +
1030 'display', 'elevation', 'emptyCells', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch',
1038 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
1031 'fontStyle', 'fontVariant', 'fontWeight', 'height', 'left', 'letterSpacing', 'lineHeight',
1039 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
1032 'listStyleImage', 'listStylePosition', 'listStyleType', 'marginBottom', 'marginLeft', 'marginRight',
1040 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
1033 'marginTop', 'markerOffset', 'marks', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'opacity',
1041 'right textIndent top width wordSpacing zIndex');
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'];
1041
1042
1042 Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1043 Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1043
1044
1045 String.__parseStyleElement = document.createElement('div');
1044 String.prototype.parseStyle = function(){
1046 String.prototype.parseStyle = function(){
1045 var element = Element.extend(document.createElement('div'));
1047 var style, styleRules = $H();
1046 element.innerHTML = '<div style="' + this + '"></div>';
1048 if (Prototype.Browser.WebKit)
1047 var style = element.down().style, styleRules = $H();
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 Element.CSS_PROPERTIES.each(function(property){
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();
1059 if (Prototype.Browser.IE && this.include('opacity'))
1054
1060 styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
1055 styleRules.each(function(pair){
1061
1056 var property = pair[0], value = pair[1], unit = null;
1062 return styleRules;
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;
1069 };
1063 };
1070
1064
1071 Element.morph = function(element, style) {
1065 if (document.defaultView && document.defaultView.getComputedStyle) {
1072 new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
1066 Element.getStyles = function(element) {
1073 return 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 };
1074 };
1084 };
1075
1085
1076 ['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
1086 Effect.Methods = {
1077 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each(
1087 morph: function(element, style) {
1078 function(f) { Element.Methods[f] = Element[f]; }
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 }
1103 };
1104
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) {
1116 $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
1082 s = effect.gsub(/_/, '-').camelize();
1117 function(f) { Effect.Methods[f] = Element[f]; }
1083 effect_class = s.charAt(0).toUpperCase() + s.substring(1);
1118 );
1084 new Effect[effect_class](element, options);
1085 return $(element);
1086 };
1087
1119
1088 Element.addMethods(); No newline at end of file
1120 Element.addMethods(Effect.Methods);
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now