214c6f68fb73cc579065212bd6ed9354f6fad370
[Plinn.git] / skins / ajax_scripts / ajax_form_manager.js
1 // (c) BenoƮt PIN 2006-2007
2 // http://plinn.org
3 // Licence GPL
4 //
5 //
6
7 var FormManager;
8
9 (function(){
10
11 FormManager = function(form, responseTextDest, lazy) {
12 if (form.elements.namedItem("noAjax")) {return;}
13
14 this.form = form;
15 this.responseTextDest = responseTextDest;
16 this.lazy = lazy;
17 var thisManager = this;
18 this.form.onsubmit = function(evt) { thisManager.submit(evt); };
19 this.form.onclick = function(evt) { thisManager.click(evt); };
20 this.submitButton = null;
21
22 /* raised on form submit */
23 this.onBeforeSubmit = null;
24 /* raised after xmlhttp response */
25 this.onResponseLoad = null;
26 /* raised when the responseText is added inside the main element.
27 * (onResponseLoad may have the default value) */
28 this.onAfterPopulate = null;
29 this.submitButton = null;
30
31 if (this.lazy) {
32 this.form.onclick = function(evt){
33 thisManager.replaceElementByField(evt);
34 thisManager.click(evt);
35 };
36 if (browser.isDOM2Event) {
37 this.form.onfocus = this.form.onclick;
38 }
39 else if (browser.isIE6up) {
40 this.form.onfocusin = this.form.onclick;
41 }
42 this.onResponseLoad = function(req){ thisManager.restoreField(req); };
43 this.lazyListeners = [];
44 }
45 };
46
47 FormManager.prototype.submit = function(evt) {
48 var form = this.form;
49 var thisManager = this;
50
51 var bsMessage; // before submit message
52 if (!this.onBeforeSubmit) {
53 var onBeforeSubmit = form.elements.namedItem("onBeforeSubmit");
54 if (onBeforeSubmit) {
55 if (onBeforeSubmit.length) {
56 onBeforeSubmit = onBeforeSubmit[0];
57 }
58 /*jslint evil: true */
59 this.onBeforeSubmit = eval(onBeforeSubmit.value);
60 bsMessage = this.onBeforeSubmit(thisManager, evt);
61 }
62 }
63 else {
64 bsMessage = this.onBeforeSubmit(thisManager, evt);
65 }
66
67 if (bsMessage === 'cancelSubmit') {
68 try {disableDefault(evt);}
69 catch (e){}
70 return;
71 }
72
73 if (!this.onResponseLoad) {
74 var onResponseLoad = form.elements.namedItem("onResponseLoad");
75 if (onResponseLoad) {
76 this.onResponseLoad = eval(onResponseLoad.value);
77 }
78 else {
79 this.onResponseLoad = this.loadResponse;
80 }
81 }
82
83 var submitButton = this.submitButton;
84 var queryInfo = this.formData2QueryString();
85 var query = queryInfo.query;
86 this.hasFile = queryInfo.hasFile;
87
88
89 if (!this.onAfterPopulate) {
90 var onAfterPopulate = form.elements.namedItem("onAfterPopulate");
91 if (onAfterPopulate) {
92 this.onAfterPopulate = onAfterPopulate.value;
93 }
94 else {
95 this.onAfterPopulate = function() {};
96 }
97 }
98
99 if (submitButton) {
100 query += submitButton.name + '=' + submitButton.value + '&';
101 }
102
103 if (window.AJAX_CONFIG && ((AJAX_CONFIG & 1) === 1)) {
104 if (form.method.toLowerCase() === 'post') {
105 this._post(query);
106 }
107 else {
108 this._get(query);
109 }
110 }
111 else {
112 this._post(query);
113 }
114
115 try {disableDefault(evt);}
116 catch (e2){}
117 };
118
119 FormManager.prototype._post = function(query) {
120 // send form by XmlHttpRequest
121 query += "ajax=1";
122
123 var req = new XMLHttpRequest();
124 var thisManager = this;
125 req.onreadystatechange = function() {
126 switch (req.readyState) {
127 case 1 :
128 showProgressImage();
129 break;
130 case 4 :
131 hideProgressImage();
132 if (req.status === 200 || req.status === 204) {
133 thisManager.onResponseLoad(req);
134 }
135 else {
136 alert('Error: ' + req.status);
137 }
138 break;
139 }
140 };
141 var url = this.form.action;
142 req.open("POST", url, true);
143 req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
144 req.send(query);
145 };
146
147 FormManager.prototype._get = function(query) {
148 // send form by browser location
149 var url = this.form.action;
150 url += '?' + query;
151 linkHandler.loadUrl(url);
152 };
153
154
155 FormManager.prototype.click = function(evt) {
156 var target = getTargetedObject(evt);
157 if(target.type === "submit" || target.type === "image") {
158 this.submitButton = target;
159 disablePropagation(evt);
160 }
161 };
162
163 FormManager.prototype.replaceElementByField = function(evt) {
164 evt = getEventObject(evt);
165 var ob = getTargetedObject(evt);
166 var eventType = evt.type;
167 if (eventType === 'focus' || eventType === 'focusin') {
168 if (this.liveFormField && ob.tagName !== 'INPUT') {
169 this.pendingEvent = [ob, 'click'];
170 }
171 return;
172 }
173 var fieldName = ob.getAttribute('id');
174 if (fieldName) {
175 this.fieldTagName = ob.tagName;
176 var tabIndex = ob.tabIndex;
177 var text;
178 if (ob.firstChild && ob.firstChild.className === 'hidden_value') {
179 text = ob.firstChild.innerHTML;
180 }
181 else {
182 text = ob.innerHTML;
183 }
184 disablePropagation(evt);
185 var parent;
186 thisManager = this;
187 switch (ob.tagName) {
188 case 'SPAN' :
189 // create input element
190 var inputText = document.createElement("input");
191 inputText.setAttribute("type", "text");
192 text = text.replace(/\n/g, ' ');
193 text = text.replace(/\s+/g, ' ');
194 text = text.replace(/^ /, '');
195 text = text.replace(/ $/, '');
196 inputText.setAttribute("value", text);
197 var inputWidth = text.length / 1.9;
198 inputWidth = (inputWidth > 5) ? inputWidth : 5;
199 inputText.style.width = inputWidth + 'em';
200 //inputText.setAttribute("size", text.length);
201
202 // replacement
203 parent = ob.parentNode;
204 parent.replaceChild(inputText, ob);
205
206 inputText.focus();
207 inputText.select();
208 inputText.setAttribute('name', fieldName);
209 inputText.tabIndex = tabIndex;
210 inputText.className = 'live_field';
211 this.liveFormField = inputText;
212 this.lazyListeners.push({'element': inputText, 'eventName' : 'blur', 'handler': function(){ thisManager.submit();}});
213 this.lazyListeners.push({'element': inputText, 'eventName' : 'keypress', 'handler': function(evt){ thisManager._fitField(evt);}});
214 this._addLazyListeners();
215 break;
216
217 case 'DIV' :
218 case 'P' :
219 // create textarea
220 var ta = document.createElement('textarea');
221 ta.style.display = 'block';
222 ta.className = 'live_field';
223 text = text.replace(/^\s*/, '');
224 text = text.replace(/\s*$/, '');
225 ta.value = text;
226
227 // replacement
228 parent = ob.parentNode;
229 parent.replaceChild(ta, ob);
230
231 ta.focus();
232 ta.select();
233 ta.setAttribute('name', fieldName);
234 ta.tabIndex = tabIndex;
235 this.liveFormField = ta;
236 this.lazyListeners.push({'element': ta, 'eventName' : 'blur', 'handler': function(){ thisManager.submit();}});
237 this._addLazyListeners();
238 break;
239 }
240 }
241 };
242
243 FormManager.prototype._addLazyListeners = function() {
244 var i, handlerInfo;
245 for (i=0 ; i<this.lazyListeners.length ; i++) {
246 handlerInfo = this.lazyListeners[i];
247 addListener(handlerInfo.element, handlerInfo.eventName, handlerInfo.handler);
248 }
249 };
250
251 FormManager.prototype._removeLazyListeners = function() {
252 var i, handlerInfo;
253 for (i=0 ; i<this.lazyListeners.length ; i++) {
254 handlerInfo = this.lazyListeners[i];
255 removeListener(handlerInfo.element, handlerInfo.eventName, handlerInfo.handler);
256 }
257 };
258
259
260 FormManager.prototype.restoreField = function(req) {
261 var text;
262 var input = this.liveFormField;
263 if (req.status === 200) {
264 if (req.getResponseHeader('Content-Type').indexOf('text/xml') !== -1) {
265 var out = '..........';
266 if (req.responseXML.documentElement.firstChild) {
267 out = req.responseXML.documentElement.firstChild.nodeValue;
268 }
269
270 switch (req.responseXML.documentElement.nodeName) {
271 case 'computedField':
272 text = out;
273 break;
274 case 'error':
275 this._removeLazyListeners();
276 alert(out);
277 this.pendingEvent = null;
278 input.focus();
279 this._addLazyListeners();
280 return false;
281 }
282 }
283 else {
284 text = req.responseText;
285 }
286 }
287 else {
288 text = '';
289 }
290
291 if (!text.match(/\w/)) {
292 text = '..........';
293 }
294
295 var field = document.createElement(this.fieldTagName);
296 field.innerHTML = text;
297 field.setAttribute('id', input.getAttribute('name'));
298 field.className = 'editable';
299 field.tabIndex = input.tabIndex;
300
301 var parent = input.parentNode;
302 parent.replaceChild(field, input);
303 this.liveFormField = null;
304
305 if (this.pendingEvent) {
306 raiseMouseEvent(this.pendingEvent[0], this.pendingEvent[1]);
307 }
308 return true;
309 };
310
311
312 FormManager.prototype.formData2QueryString = function() {
313 // http://www.onlamp.com/pub/a/onlamp/2005/05/19/xmlhttprequest.html
314 var form = this.form;
315 var strSubmit = '', formElem, elements;
316 var hasFile = false;
317 var i;
318
319 if (!this.lazy) {
320 elements = form.elements;
321 }
322 else {
323 elements = [];
324 var formElements = form.elements;
325 for (i = 0; i < formElements.length; i++) {
326 formElem = formElements[i];
327 switch (formElem.type) {
328 case 'hidden':
329 elements.push(formElem);
330 break;
331 default :
332 if (formElem === this.liveFormField) {
333 elements.push(formElem);
334 }
335 }
336 }
337 }
338
339 for (i = 0; i < elements.length; i++) {
340 formElem = elements[i];
341 switch (formElem.type) {
342 // text, select, hidden, password, textarea elements
343 case 'text':
344 case 'select-one':
345 case 'hidden':
346 case 'password':
347 case 'textarea':
348 strSubmit += formElem.name + '=' + encodeURIComponent(formElem.value) + '&';
349 break;
350 case 'radio':
351 case 'checkbox':
352 if (formElem.checked) {
353 strSubmit += formElem.name + '=' + encodeURIComponent(formElem.value) + '&';
354 }
355 break;
356 case 'select-multiple':
357 var options = formElem.getElementsByTagName("OPTION"), option;
358 var j;
359 for (j = 0 ; j < options.length ; j++) {
360 option = options[j];
361 if (option.selected) {
362 strSubmit += formElem.name + '=' + encodeURIComponent(option.value) + '&';
363 }
364 }
365 break;
366 case 'file':
367 if (formElem.value) {
368 hasFile = true;
369 }
370 break;
371 }
372 }
373 return {'query' : strSubmit, 'hasFile' : hasFile};
374 };
375
376 FormManager.prototype.loadResponse = function(req) {
377 var scripts;
378 if (req.getResponseHeader('Content-Type').indexOf('text/xml') !== -1) {
379 switch(req.responseXML.documentElement.nodeName) {
380 case 'fragments' :
381 if (this.hasFile) {
382 var sb = this.submitButton;
383 if (sb) {
384 var h = document.createElement('input');
385 h.type = 'hidden';
386 h.name = sb.name;
387 h.value = sb.value;
388 this.form.appendChild(h);
389 }
390
391 this.form.submit();
392 return;
393 }
394 var fragments = req.responseXML.documentElement.childNodes;
395 var fragment, dest;
396 var i;
397 for (i=0 ; i<fragments.length ; i++) {
398 fragment = fragments[i];
399 if (fragment.nodeName === 'fragment') {
400 dest = document.getElementById(fragment.getAttribute('id'));
401 dest.innerHTML = fragment.firstChild.nodeValue;
402
403 scripts = dest.getElementsByTagName('script');
404 var j;
405 for (j=0 ; j < scripts.length ; j++) {
406 globalScriptRegistry.loadScript(scripts[j]);
407 }
408 }
409 }
410 break;
411 case 'error':
412 alert(req.responseXML.documentElement.firstChild.nodeValue);
413 return;
414 }
415 }
416 else {
417 this.responseTextDest.innerHTML = req.responseText;
418 scripts = this.responseTextDest.getElementsByTagName('script');
419 var k;
420 for (k=0 ; k < scripts.length ; k++) {
421 globalScriptRegistry.loadScript(scripts[k]);
422 }
423 }
424
425 var onAfterPopulate = this.onAfterPopulate;
426 onAfterPopulate();
427 this.scrollToPortalMessage();
428 };
429
430 FormManager.prototype.scrollToPortalMessage = function() {
431 var psm = document.getElementById('DesktopStatusBar');
432 if (psm) {
433 var msgOffset = psm.offsetTop;
434 smoothScroll(window.scrollY, msgOffset);
435 shake(psm, 10, 1000);
436 }
437 };
438
439 FormManager.prototype._fitField = function(evt) {
440 var ob = getTargetedObject(evt);
441 var inputWidth = ob.value.length / 1.9;
442 inputWidth = (inputWidth > 5) ? inputWidth : 5;
443 ob.style.width = inputWidth + 'em';
444 };
445
446 function initForms(baseElement, lazy) {
447 if (!baseElement) {
448 baseElement = document;
449 }
450 var dest = document.getElementById("mainCell");
451 var forms = baseElement.getElementsByTagName("form");
452 var f, i;
453 for (i = 0 ; i < forms.length ; i++ ) {
454 f = new FormManager(forms[i], dest, lazy);
455 }
456 }
457
458 function smoothScroll(from, to) {
459 var intervalId;
460 var step = 25;
461 var pos = from;
462 var dir;
463 if (to > from) {
464 dir = 1;
465 }
466 else {
467 dir = -1;
468 }
469
470 var jump = function() {
471 window.scroll(0, pos);
472 pos = pos + step * dir;
473 if ((dir === 1 && pos >= to) ||
474 (dir === -1 && pos <= to)) {
475 window.clearInterval(intervalId);
476 window.scroll(0, to);
477 }
478 };
479 intervalId = window.setInterval(jump, 10);
480 }
481
482 /* adapted from http://xahlee.info/js/js_shake_box.html */
483 function shake(e, distance, time) {
484 // Handle arguments
485 if (!time) { time = 500; }
486 if (!distance) { distance = 5; }
487
488 // Save the original style of e, Make e relatively positioned, Note the animation start time, Start the animation
489 var originalStyle = e.style.cssText;
490 e.style.position = "relative";
491 var start = (new Date()).getTime();
492
493 // This function checks the elapsed time and updates the position of e.
494 // If the animation is complete, it restores e to its original state.
495 // Otherwise, it updates e's position and schedules itself to run again.
496 function animate() {
497 var now = (new Date()).getTime();
498 // Get current time
499 var elapsed = now-start;
500 // How long since we started
501 var fraction = elapsed/time;
502 // What fraction of total time?
503 if (fraction < 1) {
504 // If the animation is not yet complete
505 // Compute the x position of e as a function of animation
506 // completion fraction. We use a sinusoidal function, and multiply
507 // the completion fraction by 4pi, so that it shakes back and
508 // forth twice.
509 var x = distance * Math.sin(fraction*8*Math.PI);
510 e.style.left = x + "px";
511 // Try to run again in 25ms or at the end of the total time.
512 // We're aiming for a smooth 40 frames/second animation.
513 setTimeout(animate, Math.min(25, time-elapsed));
514 }
515 else {
516 // Otherwise, the animation is complete
517 e.style.cssText = originalStyle; // Restore the original style
518 }
519 }
520 animate();
521 }
522
523 }());