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