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