]> dev.renevier.net Git - syj.git/blob - public/js/utils.js
b16a9c2ec4206c1ec83bba7ca38e20f425585a60
[syj.git] / public / js / utils.js
1 /*  This file is part of Syj, Copyright (c) 2010 Arnaud Renevier,
2     and is published under the AGPL license. */
3
4 var CloseBtn = Class.create({
5     initialize: function(elt, options) {
6         var btn, imgsrc, style;
7
8         elt = $(elt);
9         if (!elt) {
10             return;
11         }
12
13         if (typeof options !== "object") {
14             options = {};
15         }
16
17         style = Object.extend({
18             'float': "right",
19             margin: "2px",
20             fontWeight: "bold",
21             padding: "0px"
22         }, options.style);
23
24         imgsrc = (options.closeBtnSrc) || "icons/close.png";
25         btn = new Element("input", { type: "image", src: imgsrc, alt: "X"}).setStyle(style);
26         elt.insert({top: btn});
27         btn.observe("click", function(evt) {
28             evt.stop();
29             if (evt.detail === 0) { // it's not a real click, possibly a submit event
30                 return;
31             }
32             if (typeof options.callback === "function") {
33                 options.callback.call(elt);
34             }
35             elt.hide();
36         });
37     }
38 });
39
40 var Toggler = Class.create({
41     options: {},
42
43     close: function() {
44         this.element.src = this.options.openIcn;
45         this.target.hide();
46         document.fire('toggler:close', this);
47     },
48
49     open: function() {
50         this.element.src = this.options.closeIcn;
51         this.target.show();
52         document.fire('toggler:open', this);
53     },
54
55     toggle: function(evt) {
56         if (evt && typeof evt.stop === "function") {
57             evt.stop();
58         }
59         if (this.target.visible()) {
60             this.close();
61         } else {
62             this.open();
63         }
64     },
65
66     initialize: function(target, options) {
67         this.options = Object.extend({
68                 openIcn: 'icons/bullet_arrow_right.png',
69                 closeIcn: 'icons/bullet_arrow_down.png'
70             }, options);
71
72         this.target = $(target).hide();
73         this.element = new Element("img").setStyle({ border: 'none',  // in firefox, in image inside an anchor has a border
74                                                     verticalAlign: "middle"});
75         this.element.observe('click', this.toggle.bindAsEventListener(this));
76
77         if (this.options.autoOpen) {
78             this.open();
79         } else {
80             this.close();
81         }
82     }
83 });
84
85 var Deck = Class.create({
86     initialize: function(elt, options) {
87         this.element = $(elt);
88         this.index = null;
89         this.setIndex(parseInt(this.element.readAttribute("selectedindex") || 0, 10));
90     },
91     setIndex: function(idx) {
92         if (idx === this.index) {
93             return;
94         }
95
96         var childs = this.element.childElements();
97         if (childs.length === 0) {
98             this.index = -1;
99             return;
100         }
101         idx = Math.max(0, idx);
102         idx = Math.min(childs.length - 1, idx);
103
104         childs.each(function(item, i) {
105             if (idx === i) {
106                 item.show();
107             } else {
108                 item.hide();
109             }
110         });
111         this.index = idx;
112     },
113     getIndex: function() {
114         return this.index;
115     }
116 });
117
118 Element.addMethods({
119     highlight: function(element, color, timeout) {
120         var current;
121         if (typeof timeout === "undefined") {
122             timeout = 0.3;
123         }
124         current = element.getStyle('backgroundColor');
125         Element.setStyle(element, {'backgroundColor': color});
126         Element.setStyle.delay(timeout, element, {'backgroundColor': current});
127         return element;
128     },
129     text: function(element, content) {
130         if (typeof content === "undefined") { // getter
131             if (element.nodeType === 8) {
132                 return "";
133             } else if (element.nodeType === 3 || element.nodeType === 4)  {
134                 return element.nodeValue;
135             } else {
136                 return $A(element.childNodes).inject("", function(acc, el) {
137                     return acc + Element.text(el);
138                  });
139             }
140         } else { // setter
141             var node = document.createTextNode(content);
142             element.update().appendChild(node);
143             return element;
144         }
145     }
146 });
147
148 Ajax.TimedRequest = Class.create(Ajax.Request, {
149     timeout: null,
150     delay: null,
151
152     abort: function() {
153         // see http://blog.pothoven.net/2007/12/aborting-ajax-requests-for-prototypejs.html
154         this.transport.onreadystatechange = Prototype.emptyFunction;
155         this.transport.abort();
156         Ajax.activeRequestCount--;
157     },
158
159     initialize: function($super, url, delay, options) {
160         this.delay = delay;
161         if (!options) {
162             options = {};
163         }
164
165         options.onSuccess = options.onSuccess &&
166             options.onSuccess.wrap(function(proceed, transport, json) {
167             if (this.timeout) {
168                 window.clearTimeout(this.timeout);
169                 this.timeout = null;
170             }
171             if (transport.getStatus() === 0) {
172                 this.options.onFailure(transport, json);
173             } else {
174                 proceed(transport, json);
175             }
176         }).bind(this);
177
178         options.onFailure = options.onFailure &&
179             options.onFailure.wrap(function(proceed, transport, json) {
180             if (this.timeout) {
181                 window.clearTimeout(this.timeout);
182                 this.timeout = null;
183             }
184             proceed(transport, json);
185         }).bind(this);
186
187         $super(url, options);
188     },
189
190     request: function($super, url) {
191         this.timeout = function() {
192             if (this.options.onFailure) {
193                 this.options.onFailure(null);
194             }
195             this.abort();
196         }.bind(this).delay(this.delay);
197         $super(url);
198     }
199 });
200
201 Ajax.Responders.register({
202     // needed for Ajax.TimedRequest.abort to work: see
203     // http://blog.pothoven.net/2007/12/aborting-ajax-requests-for-prototypejs.html
204     // again
205     onComplete: function() {
206         Ajax.activeRequestCount--;
207         if (Ajax.activeRequestCount < 0) {
208             Ajax.activeRequestCount = 0;
209         }
210     }
211 });
212
213 // wrapper around Form.request that sets up the submit listener, stops the
214 // submit event, calls presubmit function, calls Form.request and calls a
215 // postsubmit function
216 Element.addMethods('form', {
217     ajaxize : function(form, options) {
218         var reqoptions;
219
220         options = Object.clone(options || {});
221
222         $(form).observe('submit', function(evt) {
223             evt.stop(); // cancel form submission
224
225             reqoptions = Object.clone(options);
226             delete(reqoptions.presubmit);
227             delete(reqoptions.postsubmit);
228             delete(reqoptions.delay);
229
230             if (Object.isFunction(options.presubmit)) {
231                 if (options.presubmit(this) === false) {
232                     return;
233                 }
234             }
235
236             var params = reqoptions.parameters, action = this.readAttribute('action') || '';
237
238             if (action.blank()) {
239                 action = window.location.href;
240             }
241             reqoptions.parameters = this.serialize(true);
242
243             if (params) {
244                 if (Object.isString(params)) {
245                     params = params.toQueryParams();
246                 }
247                 Object.extend(reqoptions.parameters, params);
248             }
249
250             if (this.hasAttribute('method') && !reqoptions.method) {
251                 reqoptions.method = this.method;
252             }
253
254             if (reqoptions.onFailure) {
255                 reqoptions.onFailure = reqoptions.onFailure.wrap(function(proceed, transport, json) {
256                     form.enable();
257                     proceed(transport, json);
258                 });
259             } else {
260                 reqoptions.onFailure = function() {
261                     form.enable();
262                 };
263             }
264
265             if (reqoptions.onSuccess) {
266                 reqoptions.onSuccess = reqoptions.onSuccess.wrap(function(proceed, transport, json) {
267                     form.enable();
268                     proceed(transport, json);
269                 });
270             } else {
271                 reqoptions.onSuccess = function() {
272                     form.enable();
273                 };
274             }
275
276             new Ajax.TimedRequest(action, options.delay || 20, reqoptions);
277
278             if (Object.isFunction(options.postsubmit)) {
279                 options.postsubmit(this);
280             }
281             Form.getElements(form).each(function(elt) {
282                 elt.blur();
283                 elt.disable();
284             });
285         });
286     },
287
288     setfocus: function(form) {
289         var tofocus, error;
290
291         tofocus = null;
292         error = form.down('.error');
293         if (error) {
294             tofocus = error.previous('input,textarea');
295         } else {
296             tofocus = form.down('input:not([readonly],[disabled]),textarea:not([readonly][disabled])');
297         }
298         if (tofocus) {
299             if (error && (typeof tofocus.highlight === "function")) {
300                 tofocus.highlight('#F08080');
301             }
302             tofocus.activate();
303         }
304     },
305
306     checkEmptyElements: function(form, errorMessage) {
307         var results = [];
308         form.select('.required').each(function(elt) {
309             var id = elt.getAttribute('for'), control = $(id);
310             if (!control) {
311                 return;
312             }
313             if (!control.check(function() {
314                     return !this.value.strip().empty();
315                 }, errorMessage)) {
316                 results.push(control);
317             }
318         });
319         return results;
320     }
321 });
322
323 Element.addMethods(['input', 'textarea'], {
324     check: function(control, callback, errorMessage) {
325         if (callback.call(control)) {
326             return true;
327         }
328         control.insert({
329             after: new Element("div", {className: 'error'}).update(errorMessage)
330         });
331         return false;
332     },
333
334     observe : Element.Methods.observe.wrap(function(proceed, element, eventName, handler) {
335         if (eventName === "contentchange") {
336             proceed(element, 'keyup', function(evt) {
337                 if (evt.keyCode === 13) {
338                     return;
339                 }
340                 handler.apply(null, arguments);
341             });
342             proceed(element, 'paste', handler.defer.bind(handler));
343             return proceed(element, 'change', handler);
344         }
345         return proceed(element, eventName, handler);
346     }),
347
348     timedobserve: function(element, callback, delay) {
349         var timeout = null, initialvalue = element.value;
350
351         if (typeof delay !== "number") {
352             delay = 0.5;
353         }
354         delay = delay * 1000;
355
356         var canceltimer = function() {
357             if (timeout) {
358                 clearTimeout(timeout);
359                 timeout = null;
360             }
361         };
362         var resettimer = function() {
363             canceltimer();
364             timeout = setTimeout(triggercallback, delay);
365         };
366         var triggercallback = function() {
367             canceltimer();
368             if (initialvalue !== element.value) {
369                 initialvalue = element.value;
370                 callback.call(element);
371             }
372         };
373
374         element.observe('blur', triggercallback).
375              observe('keyup', resettimer).
376              observe('paste', resettimer);
377         return element;
378     }
379 });
380
381 Element.addMethods('div', {
382     setMessage: function(div, message, status) {
383         div.clearMessages();
384         if (status) {
385             div.setMessageStatus(status);
386         }
387         if (message) {
388             div.addMessage(message);
389         }
390         return div;
391     },
392
393     clearMessages: function(div) {
394         var node = div.firstChild, nextNode;
395
396         while (node) {
397             nextNode = node.nextSibling;
398             if (node.nodeType === 3 || node.tagName.toLowerCase() === 'br' || node.textContent || node.innerText) {
399                 div.removeChild(node);
400             }
401                 node = nextNode;
402         }
403
404         return div;
405     },
406
407     addMessage: function(div, message) {
408         var node = (div.ownerDocument || document).createTextNode(message);
409
410         if ($A(div.childNodes).filter(function(node) {
411                 return (node.nodeType === 3 || node.tagName.toLowerCase() === 'br' || node.textContent || node.innerText);
412              }).length) {
413             div.insert(new Element('br'));
414         }
415
416         div.appendChild(node);
417         return div.show();
418     },
419
420     setMessageStatus: function(div, status) {
421         return div.removeClassName('error').
422                 removeClassName('warn').
423                 removeClassName('info').
424                 removeClassName('success').
425                 addClassName(status);
426     }
427 });