|
1 /*** |
|
2 Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) |
|
3 (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) |
|
4 (c) 2005 Jon Tirsen (http://www.tirsen.com) |
|
5 Contributors: |
|
6 Richard Livsey |
|
7 Rahul Bhargava |
|
8 Rob Wills |
|
9 Mochi-ized By Thomas Herve (_firstname_@nimail.org) |
|
10 |
|
11 See scriptaculous.js for full license. |
|
12 |
|
13 Autocompleter.Base handles all the autocompletion functionality |
|
14 that's independent of the data source for autocompletion. This |
|
15 includes drawing the autocompletion menu, observing keyboard |
|
16 and mouse events, and similar. |
|
17 |
|
18 Specific autocompleters need to provide, at the very least, |
|
19 a getUpdatedChoices function that will be invoked every time |
|
20 the text inside the monitored textbox changes. This method |
|
21 should get the text for which to provide autocompletion by |
|
22 invoking this.getToken(), NOT by directly accessing |
|
23 this.element.value. This is to allow incremental tokenized |
|
24 autocompletion. Specific auto-completion logic (AJAX, etc) |
|
25 belongs in getUpdatedChoices. |
|
26 |
|
27 Tokenized incremental autocompletion is enabled automatically |
|
28 when an autocompleter is instantiated with the 'tokens' option |
|
29 in the options parameter, e.g.: |
|
30 new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); |
|
31 will incrementally autocomplete with a comma as the token. |
|
32 Additionally, ',' in the above example can be replaced with |
|
33 a token array, e.g. { tokens: [',', '\n'] } which |
|
34 enables autocompletion on multiple tokens. This is most |
|
35 useful when one of the tokens is \n (a newline), as it |
|
36 allows smart autocompletion after linebreaks. |
|
37 |
|
38 ***/ |
|
39 |
|
40 MochiKit.Base.update(MochiKit.Base, { |
|
41 ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)', |
|
42 |
|
43 /** @id MochiKit.Base.stripScripts */ |
|
44 stripScripts: function (str) { |
|
45 return str.replace(new RegExp(MochiKit.Base.ScriptFragment, 'img'), ''); |
|
46 }, |
|
47 |
|
48 /** @id MochiKit.Base.stripTags */ |
|
49 stripTags: function(str) { |
|
50 return str.replace(/<\/?[^>]+>/gi, ''); |
|
51 }, |
|
52 |
|
53 /** @id MochiKit.Base.extractScripts */ |
|
54 extractScripts: function (str) { |
|
55 var matchAll = new RegExp(MochiKit.Base.ScriptFragment, 'img'); |
|
56 var matchOne = new RegExp(MochiKit.Base.ScriptFragment, 'im'); |
|
57 return MochiKit.Base.map(function (scriptTag) { |
|
58 return (scriptTag.match(matchOne) || ['', ''])[1]; |
|
59 }, str.match(matchAll) || []); |
|
60 }, |
|
61 |
|
62 /** @id MochiKit.Base.evalScripts */ |
|
63 evalScripts: function (str) { |
|
64 return MochiKit.Base.map(function (scr) { |
|
65 eval(scr); |
|
66 }, MochiKit.Base.extractScripts(str)); |
|
67 } |
|
68 }); |
|
69 |
|
70 MochiKit.Form = { |
|
71 |
|
72 /** @id MochiKit.Form.serialize */ |
|
73 serialize: function (form) { |
|
74 var elements = MochiKit.Form.getElements(form); |
|
75 var queryComponents = []; |
|
76 |
|
77 for (var i = 0; i < elements.length; i++) { |
|
78 var queryComponent = MochiKit.Form.serializeElement(elements[i]); |
|
79 if (queryComponent) { |
|
80 queryComponents.push(queryComponent); |
|
81 } |
|
82 } |
|
83 |
|
84 return queryComponents.join('&'); |
|
85 }, |
|
86 |
|
87 /** @id MochiKit.Form.getElements */ |
|
88 getElements: function (form) { |
|
89 form = MochiKit.DOM.getElement(form); |
|
90 var elements = []; |
|
91 |
|
92 for (var tagName in MochiKit.Form.Serializers) { |
|
93 var tagElements = form.getElementsByTagName(tagName); |
|
94 for (var j = 0; j < tagElements.length; j++) { |
|
95 elements.push(tagElements[j]); |
|
96 } |
|
97 } |
|
98 return elements; |
|
99 }, |
|
100 |
|
101 /** @id MochiKit.Form.serializeElement */ |
|
102 serializeElement: function (element) { |
|
103 element = MochiKit.DOM.getElement(element); |
|
104 var method = element.tagName.toLowerCase(); |
|
105 var parameter = MochiKit.Form.Serializers[method](element); |
|
106 |
|
107 if (parameter) { |
|
108 var key = encodeURIComponent(parameter[0]); |
|
109 if (key.length === 0) { |
|
110 return; |
|
111 } |
|
112 |
|
113 if (!(parameter[1] instanceof Array)) { |
|
114 parameter[1] = [parameter[1]]; |
|
115 } |
|
116 |
|
117 return parameter[1].map(function (value) { |
|
118 return key + '=' + encodeURIComponent(value); |
|
119 }).join('&'); |
|
120 } |
|
121 } |
|
122 }; |
|
123 |
|
124 MochiKit.Form.Serializers = { |
|
125 |
|
126 /** @id MochiKit.Form.Serializers.input */ |
|
127 input: function (element) { |
|
128 switch (element.type.toLowerCase()) { |
|
129 case 'submit': |
|
130 case 'hidden': |
|
131 case 'password': |
|
132 case 'text': |
|
133 return MochiKit.Form.Serializers.textarea(element); |
|
134 case 'checkbox': |
|
135 case 'radio': |
|
136 return MochiKit.Form.Serializers.inputSelector(element); |
|
137 } |
|
138 return false; |
|
139 }, |
|
140 |
|
141 /** @id MochiKit.Form.Serializers.inputSelector */ |
|
142 inputSelector: function (element) { |
|
143 if (element.checked) { |
|
144 return [element.name, element.value]; |
|
145 } |
|
146 }, |
|
147 |
|
148 /** @id MochiKit.Form.Serializers.textarea */ |
|
149 textarea: function (element) { |
|
150 return [element.name, element.value]; |
|
151 }, |
|
152 |
|
153 /** @id MochiKit.Form.Serializers.select */ |
|
154 select: function (element) { |
|
155 return MochiKit.Form.Serializers[element.type == 'select-one' ? |
|
156 'selectOne' : 'selectMany'](element); |
|
157 }, |
|
158 |
|
159 /** @id MochiKit.Form.Serializers.selectOne */ |
|
160 selectOne: function (element) { |
|
161 var value = '', opt, index = element.selectedIndex; |
|
162 if (index >= 0) { |
|
163 opt = element.options[index]; |
|
164 value = opt.value; |
|
165 if (!value && !('value' in opt)) { |
|
166 value = opt.text; |
|
167 } |
|
168 } |
|
169 return [element.name, value]; |
|
170 }, |
|
171 |
|
172 /** @id MochiKit.Form.Serializers.selectMany */ |
|
173 selectMany: function (element) { |
|
174 var value = []; |
|
175 for (var i = 0; i < element.length; i++) { |
|
176 var opt = element.options[i]; |
|
177 if (opt.selected) { |
|
178 var optValue = opt.value; |
|
179 if (!optValue && !('value' in opt)) { |
|
180 optValue = opt.text; |
|
181 } |
|
182 value.push(optValue); |
|
183 } |
|
184 } |
|
185 return [element.name, value]; |
|
186 } |
|
187 }; |
|
188 |
|
189 /** @id Ajax */ |
|
190 var Ajax = { |
|
191 activeRequestCount: 0 |
|
192 }; |
|
193 |
|
194 Ajax.Responders = { |
|
195 responders: [], |
|
196 |
|
197 /** @id Ajax.Responders.register */ |
|
198 register: function (responderToAdd) { |
|
199 if (MochiKit.Base.find(this.responders, responderToAdd) == -1) { |
|
200 this.responders.push(responderToAdd); |
|
201 } |
|
202 }, |
|
203 |
|
204 /** @id Ajax.Responders.unregister */ |
|
205 unregister: function (responderToRemove) { |
|
206 this.responders = this.responders.without(responderToRemove); |
|
207 }, |
|
208 |
|
209 /** @id Ajax.Responders.dispatch */ |
|
210 dispatch: function (callback, request, transport, json) { |
|
211 MochiKit.Iter.forEach(this.responders, function (responder) { |
|
212 if (responder[callback] && |
|
213 typeof(responder[callback]) == 'function') { |
|
214 try { |
|
215 responder[callback].apply(responder, [request, transport, json]); |
|
216 } catch (e) {} |
|
217 } |
|
218 }); |
|
219 } |
|
220 }; |
|
221 |
|
222 Ajax.Responders.register({ |
|
223 |
|
224 /** @id Ajax.Responders.onCreate */ |
|
225 onCreate: function () { |
|
226 Ajax.activeRequestCount++; |
|
227 }, |
|
228 |
|
229 /** @id Ajax.Responders.onComplete */ |
|
230 onComplete: function () { |
|
231 Ajax.activeRequestCount--; |
|
232 } |
|
233 }); |
|
234 |
|
235 /** @id Ajax.Base */ |
|
236 Ajax.Base = function () {}; |
|
237 |
|
238 Ajax.Base.prototype = { |
|
239 |
|
240 /** @id Ajax.Base.prototype.setOptions */ |
|
241 setOptions: function (options) { |
|
242 this.options = { |
|
243 method: 'post', |
|
244 asynchronous: true, |
|
245 parameters: '' |
|
246 } |
|
247 MochiKit.Base.update(this.options, options || {}); |
|
248 }, |
|
249 |
|
250 /** @id Ajax.Base.prototype.responseIsSuccess */ |
|
251 responseIsSuccess: function () { |
|
252 return this.transport.status == undefined |
|
253 || this.transport.status === 0 |
|
254 || (this.transport.status >= 200 && this.transport.status < 300); |
|
255 }, |
|
256 |
|
257 /** @id Ajax.Base.prototype.responseIsFailure */ |
|
258 responseIsFailure: function () { |
|
259 return !this.responseIsSuccess(); |
|
260 } |
|
261 }; |
|
262 |
|
263 /** @id Ajax.Request */ |
|
264 Ajax.Request = function (url, options) { |
|
265 this.__init__(url, options); |
|
266 }; |
|
267 |
|
268 /** @id Ajax.Events */ |
|
269 Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', |
|
270 'Interactive', 'Complete']; |
|
271 |
|
272 MochiKit.Base.update(Ajax.Request.prototype, Ajax.Base.prototype); |
|
273 |
|
274 MochiKit.Base.update(Ajax.Request.prototype, { |
|
275 __init__: function (url, options) { |
|
276 this.transport = MochiKit.Async.getXMLHttpRequest(); |
|
277 this.setOptions(options); |
|
278 this.request(url); |
|
279 }, |
|
280 |
|
281 /** @id Ajax.Request.prototype.request */ |
|
282 request: function (url) { |
|
283 var parameters = this.options.parameters || ''; |
|
284 if (parameters.length > 0){ |
|
285 parameters += '&_='; |
|
286 } |
|
287 |
|
288 try { |
|
289 this.url = url; |
|
290 if (this.options.method == 'get' && parameters.length > 0) { |
|
291 this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; |
|
292 } |
|
293 Ajax.Responders.dispatch('onCreate', this, this.transport); |
|
294 |
|
295 this.transport.open(this.options.method, this.url, |
|
296 this.options.asynchronous); |
|
297 |
|
298 if (this.options.asynchronous) { |
|
299 this.transport.onreadystatechange = MochiKit.Base.bind(this.onStateChange, this); |
|
300 setTimeout(MochiKit.Base.bind(function () { |
|
301 this.respondToReadyState(1); |
|
302 }, this), 10); |
|
303 } |
|
304 |
|
305 this.setRequestHeaders(); |
|
306 |
|
307 var body = this.options.postBody ? this.options.postBody : parameters; |
|
308 this.transport.send(this.options.method == 'post' ? body : null); |
|
309 |
|
310 } catch (e) { |
|
311 this.dispatchException(e); |
|
312 } |
|
313 }, |
|
314 |
|
315 /** @id Ajax.Request.prototype.setRequestHeaders */ |
|
316 setRequestHeaders: function () { |
|
317 var requestHeaders = ['X-Requested-With', 'XMLHttpRequest']; |
|
318 |
|
319 if (this.options.method == 'post') { |
|
320 requestHeaders.push('Content-type', |
|
321 'application/x-www-form-urlencoded'); |
|
322 |
|
323 /* Force 'Connection: close' for Mozilla browsers to work around |
|
324 * a bug where XMLHttpRequest sends an incorrect Content-length |
|
325 * header. See Mozilla Bugzilla #246651. |
|
326 */ |
|
327 if (this.transport.overrideMimeType) { |
|
328 requestHeaders.push('Connection', 'close'); |
|
329 } |
|
330 } |
|
331 |
|
332 if (this.options.requestHeaders) { |
|
333 requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); |
|
334 } |
|
335 |
|
336 for (var i = 0; i < requestHeaders.length; i += 2) { |
|
337 this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); |
|
338 } |
|
339 }, |
|
340 |
|
341 /** @id Ajax.Request.prototype.onStateChange */ |
|
342 onStateChange: function () { |
|
343 var readyState = this.transport.readyState; |
|
344 if (readyState != 1) { |
|
345 this.respondToReadyState(this.transport.readyState); |
|
346 } |
|
347 }, |
|
348 |
|
349 /** @id Ajax.Request.prototype.header */ |
|
350 header: function (name) { |
|
351 try { |
|
352 return this.transport.getResponseHeader(name); |
|
353 } catch (e) {} |
|
354 }, |
|
355 |
|
356 /** @id Ajax.Request.prototype.evalJSON */ |
|
357 evalJSON: function () { |
|
358 try { |
|
359 return eval(this.header('X-JSON')); |
|
360 } catch (e) {} |
|
361 }, |
|
362 |
|
363 /** @id Ajax.Request.prototype.evalResponse */ |
|
364 evalResponse: function () { |
|
365 try { |
|
366 return eval(this.transport.responseText); |
|
367 } catch (e) { |
|
368 this.dispatchException(e); |
|
369 } |
|
370 }, |
|
371 |
|
372 /** @id Ajax.Request.prototype.respondToReadyState */ |
|
373 respondToReadyState: function (readyState) { |
|
374 var event = Ajax.Request.Events[readyState]; |
|
375 var transport = this.transport, json = this.evalJSON(); |
|
376 |
|
377 if (event == 'Complete') { |
|
378 try { |
|
379 (this.options['on' + this.transport.status] |
|
380 || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] |
|
381 || MochiKit.Base.noop)(transport, json); |
|
382 } catch (e) { |
|
383 this.dispatchException(e); |
|
384 } |
|
385 |
|
386 if ((this.header('Content-type') || '').match(/^text\/javascript/i)) { |
|
387 this.evalResponse(); |
|
388 } |
|
389 } |
|
390 |
|
391 try { |
|
392 (this.options['on' + event] || MochiKit.Base.noop)(transport, json); |
|
393 Ajax.Responders.dispatch('on' + event, this, transport, json); |
|
394 } catch (e) { |
|
395 this.dispatchException(e); |
|
396 } |
|
397 |
|
398 /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ |
|
399 if (event == 'Complete') { |
|
400 this.transport.onreadystatechange = MochiKit.Base.noop; |
|
401 } |
|
402 }, |
|
403 |
|
404 /** @id Ajax.Request.prototype.dispatchException */ |
|
405 dispatchException: function (exception) { |
|
406 (this.options.onException || MochiKit.Base.noop)(this, exception); |
|
407 Ajax.Responders.dispatch('onException', this, exception); |
|
408 } |
|
409 }); |
|
410 |
|
411 /** @id Ajax.Updater */ |
|
412 Ajax.Updater = function (container, url, options) { |
|
413 this.__init__(container, url, options); |
|
414 }; |
|
415 |
|
416 MochiKit.Base.update(Ajax.Updater.prototype, Ajax.Request.prototype); |
|
417 |
|
418 MochiKit.Base.update(Ajax.Updater.prototype, { |
|
419 __init__: function (container, url, options) { |
|
420 this.containers = { |
|
421 success: container.success ? MochiKit.DOM.getElement(container.success) : MochiKit.DOM.getElement(container), |
|
422 failure: container.failure ? MochiKit.DOM.getElement(container.failure) : |
|
423 (container.success ? null : MochiKit.DOM.getElement(container)) |
|
424 } |
|
425 this.transport = MochiKit.Async.getXMLHttpRequest(); |
|
426 this.setOptions(options); |
|
427 |
|
428 var onComplete = this.options.onComplete || MochiKit.Base.noop; |
|
429 this.options.onComplete = MochiKit.Base.bind(function (transport, object) { |
|
430 this.updateContent(); |
|
431 onComplete(transport, object); |
|
432 }, this); |
|
433 |
|
434 this.request(url); |
|
435 }, |
|
436 |
|
437 /** @id Ajax.Updater.prototype.updateContent */ |
|
438 updateContent: function () { |
|
439 var receiver = this.responseIsSuccess() ? |
|
440 this.containers.success : this.containers.failure; |
|
441 var response = this.transport.responseText; |
|
442 |
|
443 if (!this.options.evalScripts) { |
|
444 response = MochiKit.Base.stripScripts(response); |
|
445 } |
|
446 |
|
447 if (receiver) { |
|
448 if (this.options.insertion) { |
|
449 new this.options.insertion(receiver, response); |
|
450 } else { |
|
451 MochiKit.DOM.getElement(receiver).innerHTML = |
|
452 MochiKit.Base.stripScripts(response); |
|
453 setTimeout(function () { |
|
454 MochiKit.Base.evalScripts(response); |
|
455 }, 10); |
|
456 } |
|
457 } |
|
458 |
|
459 if (this.responseIsSuccess()) { |
|
460 if (this.onComplete) { |
|
461 setTimeout(MochiKit.Base.bind(this.onComplete, this), 10); |
|
462 } |
|
463 } |
|
464 } |
|
465 }); |
|
466 |
|
467 /** @id Field */ |
|
468 var Field = { |
|
469 |
|
470 /** @id clear */ |
|
471 clear: function () { |
|
472 for (var i = 0; i < arguments.length; i++) { |
|
473 MochiKit.DOM.getElement(arguments[i]).value = ''; |
|
474 } |
|
475 }, |
|
476 |
|
477 /** @id focus */ |
|
478 focus: function (element) { |
|
479 MochiKit.DOM.getElement(element).focus(); |
|
480 }, |
|
481 |
|
482 /** @id present */ |
|
483 present: function () { |
|
484 for (var i = 0; i < arguments.length; i++) { |
|
485 if (MochiKit.DOM.getElement(arguments[i]).value == '') { |
|
486 return false; |
|
487 } |
|
488 } |
|
489 return true; |
|
490 }, |
|
491 |
|
492 /** @id select */ |
|
493 select: function (element) { |
|
494 MochiKit.DOM.getElement(element).select(); |
|
495 }, |
|
496 |
|
497 /** @id activate */ |
|
498 activate: function (element) { |
|
499 element = MochiKit.DOM.getElement(element); |
|
500 element.focus(); |
|
501 if (element.select) { |
|
502 element.select(); |
|
503 } |
|
504 }, |
|
505 |
|
506 /** @id scrollFreeActivate */ |
|
507 scrollFreeActivate: function (field) { |
|
508 setTimeout(function () { |
|
509 Field.activate(field); |
|
510 }, 1); |
|
511 } |
|
512 }; |
|
513 |
|
514 |
|
515 /** @id Autocompleter */ |
|
516 var Autocompleter = {}; |
|
517 |
|
518 /** @id Autocompleter.Base */ |
|
519 Autocompleter.Base = function () {}; |
|
520 |
|
521 Autocompleter.Base.prototype = { |
|
522 |
|
523 /** @id Autocompleter.Base.prototype.baseInitialize */ |
|
524 baseInitialize: function (element, update, options) { |
|
525 this.element = MochiKit.DOM.getElement(element); |
|
526 this.update = MochiKit.DOM.getElement(update); |
|
527 this.hasFocus = false; |
|
528 this.changed = false; |
|
529 this.active = false; |
|
530 this.index = 0; |
|
531 this.entryCount = 0; |
|
532 |
|
533 if (this.setOptions) { |
|
534 this.setOptions(options); |
|
535 } |
|
536 else { |
|
537 this.options = options || {}; |
|
538 } |
|
539 |
|
540 this.options.paramName = this.options.paramName || this.element.name; |
|
541 this.options.tokens = this.options.tokens || []; |
|
542 this.options.frequency = this.options.frequency || 0.4; |
|
543 this.options.minChars = this.options.minChars || 1; |
|
544 this.options.onShow = this.options.onShow || function (element, update) { |
|
545 if (!update.style.position || update.style.position == 'absolute') { |
|
546 update.style.position = 'absolute'; |
|
547 MochiKit.Position.clone(element, update, { |
|
548 setHeight: false, |
|
549 offsetTop: element.offsetHeight |
|
550 }); |
|
551 } |
|
552 MochiKit.Visual.appear(update, {duration:0.15}); |
|
553 }; |
|
554 this.options.onHide = this.options.onHide || function (element, update) { |
|
555 MochiKit.Visual.fade(update, {duration: 0.15}); |
|
556 }; |
|
557 |
|
558 if (typeof(this.options.tokens) == 'string') { |
|
559 this.options.tokens = new Array(this.options.tokens); |
|
560 } |
|
561 |
|
562 this.observer = null; |
|
563 |
|
564 this.element.setAttribute('autocomplete', 'off'); |
|
565 |
|
566 MochiKit.Style.hideElement(this.update); |
|
567 |
|
568 MochiKit.Signal.connect(this.element, 'onblur', this, this.onBlur); |
|
569 MochiKit.Signal.connect(this.element, 'onkeypress', this, this.onKeyPress, this); |
|
570 }, |
|
571 |
|
572 /** @id Autocompleter.Base.prototype.show */ |
|
573 show: function () { |
|
574 if (MochiKit.Style.getStyle(this.update, 'display') == 'none') { |
|
575 this.options.onShow(this.element, this.update); |
|
576 } |
|
577 if (!this.iefix && /MSIE/.test(navigator.userAgent && |
|
578 (MochiKit.Style.getStyle(this.update, 'position') == 'absolute'))) { |
|
579 new Insertion.After(this.update, |
|
580 '<iframe id="' + this.update.id + '_iefix" '+ |
|
581 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + |
|
582 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); |
|
583 this.iefix = MochiKit.DOM.getElement(this.update.id + '_iefix'); |
|
584 } |
|
585 if (this.iefix) { |
|
586 setTimeout(MochiKit.Base.bind(this.fixIEOverlapping, this), 50); |
|
587 } |
|
588 }, |
|
589 |
|
590 /** @id Autocompleter.Base.prototype.fixIEOverlapping */ |
|
591 fixIEOverlapping: function () { |
|
592 MochiKit.Position.clone(this.update, this.iefix); |
|
593 this.iefix.style.zIndex = 1; |
|
594 this.update.style.zIndex = 2; |
|
595 MochiKit.Style.showElement(this.iefix); |
|
596 }, |
|
597 |
|
598 /** @id Autocompleter.Base.prototype.hide */ |
|
599 hide: function () { |
|
600 this.stopIndicator(); |
|
601 if (MochiKit.Style.getStyle(this.update, 'display') != 'none') { |
|
602 this.options.onHide(this.element, this.update); |
|
603 } |
|
604 if (this.iefix) { |
|
605 MochiKit.Style.hideElement(this.iefix); |
|
606 } |
|
607 }, |
|
608 |
|
609 /** @id Autocompleter.Base.prototype.startIndicator */ |
|
610 startIndicator: function () { |
|
611 if (this.options.indicator) { |
|
612 MochiKit.Style.showElement(this.options.indicator); |
|
613 } |
|
614 }, |
|
615 |
|
616 /** @id Autocompleter.Base.prototype.stopIndicator */ |
|
617 stopIndicator: function () { |
|
618 if (this.options.indicator) { |
|
619 MochiKit.Style.hideElement(this.options.indicator); |
|
620 } |
|
621 }, |
|
622 |
|
623 /** @id Autocompleter.Base.prototype.onKeyPress */ |
|
624 onKeyPress: function (event) { |
|
625 if (this.active) { |
|
626 if (event.key().string == "KEY_TAB" || event.key().string == "KEY_RETURN") { |
|
627 this.selectEntry(); |
|
628 MochiKit.Event.stop(event); |
|
629 } else if (event.key().string == "KEY_ESCAPE") { |
|
630 this.hide(); |
|
631 this.active = false; |
|
632 MochiKit.Event.stop(event); |
|
633 return; |
|
634 } else if (event.key().string == "KEY_LEFT" || event.key().string == "KEY_RIGHT") { |
|
635 return; |
|
636 } else if (event.key().string == "KEY_UP") { |
|
637 this.markPrevious(); |
|
638 this.render(); |
|
639 if (/AppleWebKit'/.test(navigator.appVersion)) { |
|
640 event.stop(); |
|
641 } |
|
642 return; |
|
643 } else if (event.key().string == "KEY_DOWN") { |
|
644 this.markNext(); |
|
645 this.render(); |
|
646 if (/AppleWebKit'/.test(navigator.appVersion)) { |
|
647 event.stop(); |
|
648 } |
|
649 return; |
|
650 } |
|
651 } else { |
|
652 if (event.key().string == "KEY_TAB" || event.key().string == "KEY_RETURN") { |
|
653 return; |
|
654 } |
|
655 } |
|
656 |
|
657 this.changed = true; |
|
658 this.hasFocus = true; |
|
659 |
|
660 if (this.observer) { |
|
661 clearTimeout(this.observer); |
|
662 } |
|
663 this.observer = setTimeout(MochiKit.Base.bind(this.onObserverEvent, this), |
|
664 this.options.frequency*1000); |
|
665 }, |
|
666 |
|
667 /** @id Autocompleter.Base.prototype.findElement */ |
|
668 findElement: function (event, tagName) { |
|
669 var element = event.target; |
|
670 while (element.parentNode && (!element.tagName || |
|
671 (element.tagName.toUpperCase() != tagName.toUpperCase()))) { |
|
672 element = element.parentNode; |
|
673 } |
|
674 return element; |
|
675 }, |
|
676 |
|
677 /** @id Autocompleter.Base.prototype.hover */ |
|
678 onHover: function (event) { |
|
679 var element = this.findElement(event, 'LI'); |
|
680 if (this.index != element.autocompleteIndex) { |
|
681 this.index = element.autocompleteIndex; |
|
682 this.render(); |
|
683 } |
|
684 event.stop(); |
|
685 }, |
|
686 |
|
687 /** @id Autocompleter.Base.prototype.onClick */ |
|
688 onClick: function (event) { |
|
689 var element = this.findElement(event, 'LI'); |
|
690 this.index = element.autocompleteIndex; |
|
691 this.selectEntry(); |
|
692 this.hide(); |
|
693 }, |
|
694 |
|
695 /** @id Autocompleter.Base.prototype.onBlur */ |
|
696 onBlur: function (event) { |
|
697 // needed to make click events working |
|
698 setTimeout(MochiKit.Base.bind(this.hide, this), 250); |
|
699 this.hasFocus = false; |
|
700 this.active = false; |
|
701 }, |
|
702 |
|
703 /** @id Autocompleter.Base.prototype.render */ |
|
704 render: function () { |
|
705 if (this.entryCount > 0) { |
|
706 for (var i = 0; i < this.entryCount; i++) { |
|
707 this.index == i ? |
|
708 MochiKit.DOM.addElementClass(this.getEntry(i), 'selected') : |
|
709 MochiKit.DOM.removeElementClass(this.getEntry(i), 'selected'); |
|
710 } |
|
711 if (this.hasFocus) { |
|
712 this.show(); |
|
713 this.active = true; |
|
714 } |
|
715 } else { |
|
716 this.active = false; |
|
717 this.hide(); |
|
718 } |
|
719 }, |
|
720 |
|
721 /** @id Autocompleter.Base.prototype.markPrevious */ |
|
722 markPrevious: function () { |
|
723 if (this.index > 0) { |
|
724 this.index-- |
|
725 } else { |
|
726 this.index = this.entryCount-1; |
|
727 } |
|
728 }, |
|
729 |
|
730 /** @id Autocompleter.Base.prototype.markNext */ |
|
731 markNext: function () { |
|
732 if (this.index < this.entryCount-1) { |
|
733 this.index++ |
|
734 } else { |
|
735 this.index = 0; |
|
736 } |
|
737 }, |
|
738 |
|
739 /** @id Autocompleter.Base.prototype.getEntry */ |
|
740 getEntry: function (index) { |
|
741 return this.update.firstChild.childNodes[index]; |
|
742 }, |
|
743 |
|
744 /** @id Autocompleter.Base.prototype.getCurrentEntry */ |
|
745 getCurrentEntry: function () { |
|
746 return this.getEntry(this.index); |
|
747 }, |
|
748 |
|
749 /** @id Autocompleter.Base.prototype.selectEntry */ |
|
750 selectEntry: function () { |
|
751 this.active = false; |
|
752 this.updateElement(this.getCurrentEntry()); |
|
753 }, |
|
754 |
|
755 /** @id Autocompleter.Base.prototype.collectTextNodesIgnoreClass */ |
|
756 collectTextNodesIgnoreClass: function (element, className) { |
|
757 return MochiKit.Base.flattenArray(MochiKit.Base.map(function (node) { |
|
758 if (node.nodeType == 3) { |
|
759 return node.nodeValue; |
|
760 } else if (node.hasChildNodes() && !MochiKit.DOM.hasElementClass(node, className)) { |
|
761 return this.collectTextNodesIgnoreClass(node, className); |
|
762 } |
|
763 return ''; |
|
764 }, MochiKit.DOM.getElement(element).childNodes)).join(''); |
|
765 }, |
|
766 |
|
767 /** @id Autocompleter.Base.prototype.updateElement */ |
|
768 updateElement: function (selectedElement) { |
|
769 if (this.options.updateElement) { |
|
770 this.options.updateElement(selectedElement); |
|
771 return; |
|
772 } |
|
773 var value = ''; |
|
774 if (this.options.select) { |
|
775 var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; |
|
776 if (nodes.length > 0) { |
|
777 value = MochiKit.DOM.scrapeText(nodes[0]); |
|
778 } |
|
779 } else { |
|
780 value = this.collectTextNodesIgnoreClass(selectedElement, 'informal'); |
|
781 } |
|
782 var lastTokenPos = this.findLastToken(); |
|
783 if (lastTokenPos != -1) { |
|
784 var newValue = this.element.value.substr(0, lastTokenPos + 1); |
|
785 var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); |
|
786 if (whitespace) { |
|
787 newValue += whitespace[0]; |
|
788 } |
|
789 this.element.value = newValue + value; |
|
790 } else { |
|
791 this.element.value = value; |
|
792 } |
|
793 this.element.focus(); |
|
794 |
|
795 if (this.options.afterUpdateElement) { |
|
796 this.options.afterUpdateElement(this.element, selectedElement); |
|
797 } |
|
798 }, |
|
799 |
|
800 /** @id Autocompleter.Base.prototype.updateChoices */ |
|
801 updateChoices: function (choices) { |
|
802 if (!this.changed && this.hasFocus) { |
|
803 this.update.innerHTML = choices; |
|
804 var d = MochiKit.DOM; |
|
805 d.removeEmptyTextNodes(this.update); |
|
806 d.removeEmptyTextNodes(this.update.firstChild); |
|
807 |
|
808 if (this.update.firstChild && this.update.firstChild.childNodes) { |
|
809 this.entryCount = this.update.firstChild.childNodes.length; |
|
810 for (var i = 0; i < this.entryCount; i++) { |
|
811 var entry = this.getEntry(i); |
|
812 entry.autocompleteIndex = i; |
|
813 this.addObservers(entry); |
|
814 } |
|
815 } else { |
|
816 this.entryCount = 0; |
|
817 } |
|
818 |
|
819 this.stopIndicator(); |
|
820 |
|
821 this.index = 0; |
|
822 this.render(); |
|
823 } |
|
824 }, |
|
825 |
|
826 /** @id Autocompleter.Base.prototype.addObservers */ |
|
827 addObservers: function (element) { |
|
828 MochiKit.Signal.connect(element, 'onmouseover', this, this.onHover); |
|
829 MochiKit.Signal.connect(element, 'onclick', this, this.onClick); |
|
830 }, |
|
831 |
|
832 /** @id Autocompleter.Base.prototype.onObserverEvent */ |
|
833 onObserverEvent: function () { |
|
834 this.changed = false; |
|
835 if (this.getToken().length >= this.options.minChars) { |
|
836 this.startIndicator(); |
|
837 this.getUpdatedChoices(); |
|
838 } else { |
|
839 this.active = false; |
|
840 this.hide(); |
|
841 } |
|
842 }, |
|
843 |
|
844 /** @id Autocompleter.Base.prototype.getToken */ |
|
845 getToken: function () { |
|
846 var tokenPos = this.findLastToken(); |
|
847 if (tokenPos != -1) { |
|
848 var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); |
|
849 } else { |
|
850 var ret = this.element.value; |
|
851 } |
|
852 return /\n/.test(ret) ? '' : ret; |
|
853 }, |
|
854 |
|
855 /** @id Autocompleter.Base.prototype.findLastToken */ |
|
856 findLastToken: function () { |
|
857 var lastTokenPos = -1; |
|
858 |
|
859 for (var i = 0; i < this.options.tokens.length; i++) { |
|
860 var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]); |
|
861 if (thisTokenPos > lastTokenPos) { |
|
862 lastTokenPos = thisTokenPos; |
|
863 } |
|
864 } |
|
865 return lastTokenPos; |
|
866 } |
|
867 } |
|
868 |
|
869 /** @id Ajax.Autocompleter */ |
|
870 Ajax.Autocompleter = function (element, update, url, options) { |
|
871 this.__init__(element, update, url, options); |
|
872 }; |
|
873 |
|
874 MochiKit.Base.update(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype); |
|
875 |
|
876 MochiKit.Base.update(Ajax.Autocompleter.prototype, { |
|
877 __init__: function (element, update, url, options) { |
|
878 this.baseInitialize(element, update, options); |
|
879 this.options.asynchronous = true; |
|
880 this.options.onComplete = MochiKit.Base.bind(this.onComplete, this); |
|
881 this.options.defaultParams = this.options.parameters || null; |
|
882 this.url = url; |
|
883 }, |
|
884 |
|
885 /** @id Ajax.Autocompleter.prototype.getUpdatedChoices */ |
|
886 getUpdatedChoices: function () { |
|
887 var entry = encodeURIComponent(this.options.paramName) + '=' + |
|
888 encodeURIComponent(this.getToken()); |
|
889 |
|
890 this.options.parameters = this.options.callback ? |
|
891 this.options.callback(this.element, entry) : entry; |
|
892 |
|
893 if (this.options.defaultParams) { |
|
894 this.options.parameters += '&' + this.options.defaultParams; |
|
895 } |
|
896 new Ajax.Request(this.url, this.options); |
|
897 }, |
|
898 |
|
899 /** @id Ajax.Autocompleter.prototype.onComplete */ |
|
900 onComplete: function (request) { |
|
901 this.updateChoices(request.responseText); |
|
902 } |
|
903 }); |
|
904 |
|
905 /*** |
|
906 |
|
907 The local array autocompleter. Used when you'd prefer to |
|
908 inject an array of autocompletion options into the page, rather |
|
909 than sending out Ajax queries, which can be quite slow sometimes. |
|
910 |
|
911 The constructor takes four parameters. The first two are, as usual, |
|
912 the id of the monitored textbox, and id of the autocompletion menu. |
|
913 The third is the array you want to autocomplete from, and the fourth |
|
914 is the options block. |
|
915 |
|
916 Extra local autocompletion options: |
|
917 - choices - How many autocompletion choices to offer |
|
918 |
|
919 - partialSearch - If false, the autocompleter will match entered |
|
920 text only at the beginning of strings in the |
|
921 autocomplete array. Defaults to true, which will |
|
922 match text at the beginning of any *word* in the |
|
923 strings in the autocomplete array. If you want to |
|
924 search anywhere in the string, additionally set |
|
925 the option fullSearch to true (default: off). |
|
926 |
|
927 - fullSsearch - Search anywhere in autocomplete array strings. |
|
928 |
|
929 - partialChars - How many characters to enter before triggering |
|
930 a partial match (unlike minChars, which defines |
|
931 how many characters are required to do any match |
|
932 at all). Defaults to 2. |
|
933 |
|
934 - ignoreCase - Whether to ignore case when autocompleting. |
|
935 Defaults to true. |
|
936 |
|
937 It's possible to pass in a custom function as the 'selector' |
|
938 option, if you prefer to write your own autocompletion logic. |
|
939 In that case, the other options above will not apply unless |
|
940 you support them. |
|
941 |
|
942 ***/ |
|
943 |
|
944 /** @id Autocompleter.Local */ |
|
945 Autocompleter.Local = function (element, update, array, options) { |
|
946 this.__init__(element, update, array, options); |
|
947 }; |
|
948 |
|
949 MochiKit.Base.update(Autocompleter.Local.prototype, Autocompleter.Base.prototype); |
|
950 |
|
951 MochiKit.Base.update(Autocompleter.Local.prototype, { |
|
952 __init__: function (element, update, array, options) { |
|
953 this.baseInitialize(element, update, options); |
|
954 this.options.array = array; |
|
955 }, |
|
956 |
|
957 /** @id Autocompleter.Local.prototype.getUpdatedChoices */ |
|
958 getUpdatedChoices: function () { |
|
959 this.updateChoices(this.options.selector(this)); |
|
960 }, |
|
961 |
|
962 /** @id Autocompleter.Local.prototype.setOptions */ |
|
963 setOptions: function (options) { |
|
964 this.options = MochiKit.Base.update({ |
|
965 choices: 10, |
|
966 partialSearch: true, |
|
967 partialChars: 2, |
|
968 ignoreCase: true, |
|
969 fullSearch: false, |
|
970 selector: function (instance) { |
|
971 var ret = []; // Beginning matches |
|
972 var partial = []; // Inside matches |
|
973 var entry = instance.getToken(); |
|
974 var count = 0; |
|
975 |
|
976 for (var i = 0; i < instance.options.array.length && |
|
977 ret.length < instance.options.choices ; i++) { |
|
978 |
|
979 var elem = instance.options.array[i]; |
|
980 var foundPos = instance.options.ignoreCase ? |
|
981 elem.toLowerCase().indexOf(entry.toLowerCase()) : |
|
982 elem.indexOf(entry); |
|
983 |
|
984 while (foundPos != -1) { |
|
985 if (foundPos === 0 && elem.length != entry.length) { |
|
986 ret.push('<li><strong>' + elem.substr(0, entry.length) + '</strong>' + |
|
987 elem.substr(entry.length) + '</li>'); |
|
988 break; |
|
989 } else if (entry.length >= instance.options.partialChars && |
|
990 instance.options.partialSearch && foundPos != -1) { |
|
991 if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos - 1, 1))) { |
|
992 partial.push('<li>' + elem.substr(0, foundPos) + '<strong>' + |
|
993 elem.substr(foundPos, entry.length) + '</strong>' + elem.substr( |
|
994 foundPos + entry.length) + '</li>'); |
|
995 break; |
|
996 } |
|
997 } |
|
998 |
|
999 foundPos = instance.options.ignoreCase ? |
|
1000 elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : |
|
1001 elem.indexOf(entry, foundPos + 1); |
|
1002 |
|
1003 } |
|
1004 } |
|
1005 if (partial.length) { |
|
1006 ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) |
|
1007 } |
|
1008 return '<ul>' + ret.join('') + '</ul>'; |
|
1009 } |
|
1010 }, options || {}); |
|
1011 } |
|
1012 }); |
|
1013 |
|
1014 /*** |
|
1015 |
|
1016 AJAX in-place editor |
|
1017 |
|
1018 see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor |
|
1019 |
|
1020 Use this if you notice weird scrolling problems on some browsers, |
|
1021 the DOM might be a bit confused when this gets called so do this |
|
1022 waits 1 ms (with setTimeout) until it does the activation |
|
1023 |
|
1024 ***/ |
|
1025 |
|
1026 /** @id Ajax.InPlaceEditor */ |
|
1027 Ajax.InPlaceEditor = function (element, url, options) { |
|
1028 this.__init__(element, url, options); |
|
1029 }; |
|
1030 |
|
1031 /** @id Ajax.InPlaceEditor.defaultHighlightColor */ |
|
1032 Ajax.InPlaceEditor.defaultHighlightColor = '#FFFF99'; |
|
1033 |
|
1034 Ajax.InPlaceEditor.prototype = { |
|
1035 __init__: function (element, url, options) { |
|
1036 this.url = url; |
|
1037 this.element = MochiKit.DOM.getElement(element); |
|
1038 |
|
1039 this.options = MochiKit.Base.update({ |
|
1040 okButton: true, |
|
1041 okText: 'ok', |
|
1042 cancelLink: true, |
|
1043 cancelText: 'cancel', |
|
1044 savingText: 'Saving...', |
|
1045 clickToEditText: 'Click to edit', |
|
1046 okText: 'ok', |
|
1047 rows: 1, |
|
1048 onComplete: function (transport, element) { |
|
1049 new MochiKit.Visual.Highlight(element, {startcolor: this.options.highlightcolor}); |
|
1050 }, |
|
1051 onFailure: function (transport) { |
|
1052 alert('Error communicating with the server: ' + MochiKit.Base.stripTags(transport.responseText)); |
|
1053 }, |
|
1054 callback: function (form) { |
|
1055 return MochiKit.DOM.formContents(form); |
|
1056 }, |
|
1057 handleLineBreaks: true, |
|
1058 loadingText: 'Loading...', |
|
1059 savingClassName: 'inplaceeditor-saving', |
|
1060 loadingClassName: 'inplaceeditor-loading', |
|
1061 formClassName: 'inplaceeditor-form', |
|
1062 highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, |
|
1063 highlightendcolor: '#FFFFFF', |
|
1064 externalControl: null, |
|
1065 submitOnBlur: false, |
|
1066 ajaxOptions: {} |
|
1067 }, options || {}); |
|
1068 |
|
1069 if (!this.options.formId && this.element.id) { |
|
1070 this.options.formId = this.element.id + '-inplaceeditor'; |
|
1071 if (MochiKit.DOM.getElement(this.options.formId)) { |
|
1072 // there's already a form with that name, don't specify an id |
|
1073 this.options.formId = null; |
|
1074 } |
|
1075 } |
|
1076 |
|
1077 if (this.options.externalControl) { |
|
1078 this.options.externalControl = MochiKit.DOM.getElement(this.options.externalControl); |
|
1079 } |
|
1080 |
|
1081 this.originalBackground = MochiKit.Style.getStyle(this.element, 'background-color'); |
|
1082 if (!this.originalBackground) { |
|
1083 this.originalBackground = 'transparent'; |
|
1084 } |
|
1085 |
|
1086 this.element.title = this.options.clickToEditText; |
|
1087 |
|
1088 this.onclickListener = MochiKit.Signal.connect(this.element, 'onclick', this, this.enterEditMode); |
|
1089 this.mouseoverListener = MochiKit.Signal.connect(this.element, 'onmouseover', this, this.enterHover); |
|
1090 this.mouseoutListener = MochiKit.Signal.connect(this.element, 'onmouseout', this, this.leaveHover); |
|
1091 if (this.options.externalControl) { |
|
1092 this.onclickListenerExternal = MochiKit.Signal.connect(this.options.externalControl, |
|
1093 'onclick', this, this.enterEditMode); |
|
1094 this.mouseoverListenerExternal = MochiKit.Signal.connect(this.options.externalControl, |
|
1095 'onmouseover', this, this.enterHover); |
|
1096 this.mouseoutListenerExternal = MochiKit.Signal.connect(this.options.externalControl, |
|
1097 'onmouseout', this, this.leaveHover); |
|
1098 } |
|
1099 }, |
|
1100 |
|
1101 /** @id Ajax.InPlaceEditor.prototype.enterEditMode */ |
|
1102 enterEditMode: function (evt) { |
|
1103 if (this.saving) { |
|
1104 return; |
|
1105 } |
|
1106 if (this.editing) { |
|
1107 return; |
|
1108 } |
|
1109 this.editing = true; |
|
1110 this.onEnterEditMode(); |
|
1111 if (this.options.externalControl) { |
|
1112 MochiKit.Style.hideElement(this.options.externalControl); |
|
1113 } |
|
1114 MochiKit.Style.hideElement(this.element); |
|
1115 this.createForm(); |
|
1116 this.element.parentNode.insertBefore(this.form, this.element); |
|
1117 Field.scrollFreeActivate(this.editField); |
|
1118 // stop the event to avoid a page refresh in Safari |
|
1119 if (evt) { |
|
1120 evt.stop(); |
|
1121 } |
|
1122 return false; |
|
1123 }, |
|
1124 |
|
1125 /** @id Ajax.InPlaceEditor.prototype.createForm */ |
|
1126 createForm: function () { |
|
1127 this.form = document.createElement('form'); |
|
1128 this.form.id = this.options.formId; |
|
1129 MochiKit.DOM.addElementClass(this.form, this.options.formClassName) |
|
1130 this.form.onsubmit = MochiKit.Base.bind(this.onSubmit, this); |
|
1131 |
|
1132 this.createEditField(); |
|
1133 |
|
1134 if (this.options.textarea) { |
|
1135 var br = document.createElement('br'); |
|
1136 this.form.appendChild(br); |
|
1137 } |
|
1138 |
|
1139 if (this.options.okButton) { |
|
1140 okButton = document.createElement('input'); |
|
1141 okButton.type = 'submit'; |
|
1142 okButton.value = this.options.okText; |
|
1143 this.form.appendChild(okButton); |
|
1144 } |
|
1145 |
|
1146 if (this.options.cancelLink) { |
|
1147 cancelLink = document.createElement('a'); |
|
1148 cancelLink.href = '#'; |
|
1149 cancelLink.appendChild(document.createTextNode(this.options.cancelText)); |
|
1150 cancelLink.onclick = MochiKit.Base.bind(this.onclickCancel, this); |
|
1151 this.form.appendChild(cancelLink); |
|
1152 } |
|
1153 }, |
|
1154 |
|
1155 /** @id Ajax.InPlaceEditor.prototype.hasHTMLLineBreaks */ |
|
1156 hasHTMLLineBreaks: function (string) { |
|
1157 if (!this.options.handleLineBreaks) { |
|
1158 return false; |
|
1159 } |
|
1160 return string.match(/<br/i) || string.match(/<p>/i); |
|
1161 }, |
|
1162 |
|
1163 /** @id Ajax.InPlaceEditor.prototype.convertHTMLLineBreaks */ |
|
1164 convertHTMLLineBreaks: function (string) { |
|
1165 return string.replace(/<br>/gi, '\n').replace(/<br\/>/gi, '\n').replace(/<\/p>/gi, '\n').replace(/<p>/gi, ''); |
|
1166 }, |
|
1167 |
|
1168 /** @id Ajax.InPlaceEditor.prototype.createEditField */ |
|
1169 createEditField: function () { |
|
1170 var text; |
|
1171 if (this.options.loadTextURL) { |
|
1172 text = this.options.loadingText; |
|
1173 } else { |
|
1174 text = this.getText(); |
|
1175 } |
|
1176 |
|
1177 var obj = this; |
|
1178 |
|
1179 if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { |
|
1180 this.options.textarea = false; |
|
1181 var textField = document.createElement('input'); |
|
1182 textField.obj = this; |
|
1183 textField.type = 'text'; |
|
1184 textField.name = 'value'; |
|
1185 textField.value = text; |
|
1186 textField.style.backgroundColor = this.options.highlightcolor; |
|
1187 var size = this.options.size || this.options.cols || 0; |
|
1188 if (size !== 0) { |
|
1189 textField.size = size; |
|
1190 } |
|
1191 if (this.options.submitOnBlur) { |
|
1192 textField.onblur = MochiKit.Base.bind(this.onSubmit, this); |
|
1193 } |
|
1194 this.editField = textField; |
|
1195 } else { |
|
1196 this.options.textarea = true; |
|
1197 var textArea = document.createElement('textarea'); |
|
1198 textArea.obj = this; |
|
1199 textArea.name = 'value'; |
|
1200 textArea.value = this.convertHTMLLineBreaks(text); |
|
1201 textArea.rows = this.options.rows; |
|
1202 textArea.cols = this.options.cols || 40; |
|
1203 if (this.options.submitOnBlur) { |
|
1204 textArea.onblur = MochiKit.Base.bind(this.onSubmit, this); |
|
1205 } |
|
1206 this.editField = textArea; |
|
1207 } |
|
1208 |
|
1209 if (this.options.loadTextURL) { |
|
1210 this.loadExternalText(); |
|
1211 } |
|
1212 this.form.appendChild(this.editField); |
|
1213 }, |
|
1214 |
|
1215 /** @id Ajax.InPlaceEditor.prototype.getText */ |
|
1216 getText: function () { |
|
1217 return this.element.innerHTML; |
|
1218 }, |
|
1219 |
|
1220 /** @id Ajax.InPlaceEditor.prototype.loadExternalText */ |
|
1221 loadExternalText: function () { |
|
1222 MochiKit.DOM.addElementClass(this.form, this.options.loadingClassName); |
|
1223 this.editField.disabled = true; |
|
1224 new Ajax.Request( |
|
1225 this.options.loadTextURL, |
|
1226 MochiKit.Base.update({ |
|
1227 asynchronous: true, |
|
1228 onComplete: MochiKit.Base.bind(this.onLoadedExternalText, this) |
|
1229 }, this.options.ajaxOptions) |
|
1230 ); |
|
1231 }, |
|
1232 |
|
1233 /** @id Ajax.InPlaceEditor.prototype.onLoadedExternalText */ |
|
1234 onLoadedExternalText: function (transport) { |
|
1235 MochiKit.DOM.removeElementClass(this.form, this.options.loadingClassName); |
|
1236 this.editField.disabled = false; |
|
1237 this.editField.value = MochiKit.Base.stripTags(transport); |
|
1238 }, |
|
1239 |
|
1240 /** @id Ajax.InPlaceEditor.prototype.onclickCancel */ |
|
1241 onclickCancel: function () { |
|
1242 this.onComplete(); |
|
1243 this.leaveEditMode(); |
|
1244 return false; |
|
1245 }, |
|
1246 |
|
1247 /** @id Ajax.InPlaceEditor.prototype.onFailure */ |
|
1248 onFailure: function (transport) { |
|
1249 this.options.onFailure(transport); |
|
1250 if (this.oldInnerHTML) { |
|
1251 this.element.innerHTML = this.oldInnerHTML; |
|
1252 this.oldInnerHTML = null; |
|
1253 } |
|
1254 return false; |
|
1255 }, |
|
1256 |
|
1257 /** @id Ajax.InPlaceEditor.prototype.onSubmit */ |
|
1258 onSubmit: function () { |
|
1259 // onLoading resets these so we need to save them away for the Ajax call |
|
1260 var form = this.form; |
|
1261 var value = this.editField.value; |
|
1262 |
|
1263 // do this first, sometimes the ajax call returns before we get a |
|
1264 // chance to switch on Saving which means this will actually switch on |
|
1265 // Saving *after* we have left edit mode causing Saving to be |
|
1266 // displayed indefinitely |
|
1267 this.onLoading(); |
|
1268 |
|
1269 new Ajax.Updater( |
|
1270 { |
|
1271 success: this.element, |
|
1272 // dont update on failure (this could be an option) |
|
1273 failure: null |
|
1274 }, |
|
1275 this.url, |
|
1276 MochiKit.Base.update({ |
|
1277 parameters: this.options.callback(form, value), |
|
1278 onComplete: MochiKit.Base.bind(this.onComplete, this), |
|
1279 onFailure: MochiKit.Base.bind(this.onFailure, this) |
|
1280 }, this.options.ajaxOptions) |
|
1281 ); |
|
1282 // stop the event to avoid a page refresh in Safari |
|
1283 if (arguments.length > 1) { |
|
1284 arguments[0].stop(); |
|
1285 } |
|
1286 return false; |
|
1287 }, |
|
1288 |
|
1289 /** @id Ajax.InPlaceEditor.prototype.onLoading */ |
|
1290 onLoading: function () { |
|
1291 this.saving = true; |
|
1292 this.removeForm(); |
|
1293 this.leaveHover(); |
|
1294 this.showSaving(); |
|
1295 }, |
|
1296 |
|
1297 /** @id Ajax.InPlaceEditor.prototype.onSaving */ |
|
1298 showSaving: function () { |
|
1299 this.oldInnerHTML = this.element.innerHTML; |
|
1300 this.element.innerHTML = this.options.savingText; |
|
1301 MochiKit.DOM.addElementClass(this.element, this.options.savingClassName); |
|
1302 this.element.style.backgroundColor = this.originalBackground; |
|
1303 MochiKit.Style.showElement(this.element); |
|
1304 }, |
|
1305 |
|
1306 /** @id Ajax.InPlaceEditor.prototype.removeForm */ |
|
1307 removeForm: function () { |
|
1308 if (this.form) { |
|
1309 if (this.form.parentNode) { |
|
1310 MochiKit.DOM.removeElement(this.form); |
|
1311 } |
|
1312 this.form = null; |
|
1313 } |
|
1314 }, |
|
1315 |
|
1316 /** @id Ajax.InPlaceEditor.prototype.enterHover */ |
|
1317 enterHover: function () { |
|
1318 if (this.saving) { |
|
1319 return; |
|
1320 } |
|
1321 this.element.style.backgroundColor = this.options.highlightcolor; |
|
1322 if (this.effect) { |
|
1323 this.effect.cancel(); |
|
1324 } |
|
1325 MochiKit.DOM.addElementClass(this.element, this.options.hoverClassName) |
|
1326 }, |
|
1327 |
|
1328 /** @id Ajax.InPlaceEditor.prototype.leaveHover */ |
|
1329 leaveHover: function () { |
|
1330 if (this.options.backgroundColor) { |
|
1331 this.element.style.backgroundColor = this.oldBackground; |
|
1332 } |
|
1333 MochiKit.DOM.removeElementClass(this.element, this.options.hoverClassName) |
|
1334 if (this.saving) { |
|
1335 return; |
|
1336 } |
|
1337 this.effect = new MochiKit.Visual.Highlight(this.element, { |
|
1338 startcolor: this.options.highlightcolor, |
|
1339 endcolor: this.options.highlightendcolor, |
|
1340 restorecolor: this.originalBackground |
|
1341 }); |
|
1342 }, |
|
1343 |
|
1344 /** @id Ajax.InPlaceEditor.prototype.leaveEditMode */ |
|
1345 leaveEditMode: function () { |
|
1346 MochiKit.DOM.removeElementClass(this.element, this.options.savingClassName); |
|
1347 this.removeForm(); |
|
1348 this.leaveHover(); |
|
1349 this.element.style.backgroundColor = this.originalBackground; |
|
1350 MochiKit.Style.showElement(this.element); |
|
1351 if (this.options.externalControl) { |
|
1352 MochiKit.Style.showElement(this.options.externalControl); |
|
1353 } |
|
1354 this.editing = false; |
|
1355 this.saving = false; |
|
1356 this.oldInnerHTML = null; |
|
1357 this.onLeaveEditMode(); |
|
1358 }, |
|
1359 |
|
1360 /** @id Ajax.InPlaceEditor.prototype.onComplete */ |
|
1361 onComplete: function (transport) { |
|
1362 this.leaveEditMode(); |
|
1363 MochiKit.Base.bind(this.options.onComplete, this)(transport, this.element); |
|
1364 }, |
|
1365 |
|
1366 /** @id Ajax.InPlaceEditor.prototype.onEnterEditMode */ |
|
1367 onEnterEditMode: function () {}, |
|
1368 |
|
1369 /** @id Ajax.InPlaceEditor.prototype.onLeaveEditMode */ |
|
1370 onLeaveEditMode: function () {}, |
|
1371 |
|
1372 /** @id Ajax.InPlaceEditor.prototype.dispose */ |
|
1373 dispose: function () { |
|
1374 if (this.oldInnerHTML) { |
|
1375 this.element.innerHTML = this.oldInnerHTML; |
|
1376 } |
|
1377 this.leaveEditMode(); |
|
1378 MochiKit.Signal.disconnect(this.onclickListener); |
|
1379 MochiKit.Signal.disconnect(this.mouseoverListener); |
|
1380 MochiKit.Signal.disconnect(this.mouseoutListener); |
|
1381 if (this.options.externalControl) { |
|
1382 MochiKit.Signal.disconnect(this.onclickListenerExternal); |
|
1383 MochiKit.Signal.disconnect(this.mouseoverListenerExternal); |
|
1384 MochiKit.Signal.disconnect(this.mouseoutListenerExternal); |
|
1385 } |
|
1386 } |
|
1387 }; |
|
1388 |