diff options
author | Mike Crute <mcrute@gmail.com> | 2010-01-17 12:06:15 -0500 |
---|---|---|
committer | Mike Crute <mcrute@gmail.com> | 2010-01-17 12:06:15 -0500 |
commit | 55974d21a824378b287e563bce4c32597060cfca (patch) | |
tree | ff8426efe10ff98189fee26583f061fb08f19a50 /docroot/lib/scriptaculous/controls.js | |
download | designer_site-55974d21a824378b287e563bce4c32597060cfca.tar.bz2 designer_site-55974d21a824378b287e563bce4c32597060cfca.tar.xz designer_site-55974d21a824378b287e563bce4c32597060cfca.zip |
Diffstat (limited to 'docroot/lib/scriptaculous/controls.js')
-rwxr-xr-x | docroot/lib/scriptaculous/controls.js | 875 |
1 files changed, 875 insertions, 0 deletions
diff --git a/docroot/lib/scriptaculous/controls.js b/docroot/lib/scriptaculous/controls.js new file mode 100755 index 0000000..6783bd0 --- /dev/null +++ b/docroot/lib/scriptaculous/controls.js | |||
@@ -0,0 +1,875 @@ | |||
1 | // script.aculo.us controls.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 | ||
2 | |||
3 | // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) | ||
4 | // (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) | ||
5 | // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) | ||
6 | // Contributors: | ||
7 | // Richard Livsey | ||
8 | // Rahul Bhargava | ||
9 | // Rob Wills | ||
10 | // | ||
11 | // script.aculo.us is freely distributable under the terms of an MIT-style license. | ||
12 | // For details, see the script.aculo.us web site: http://script.aculo.us/ | ||
13 | |||
14 | // Autocompleter.Base handles all the autocompletion functionality | ||
15 | // that's independent of the data source for autocompletion. This | ||
16 | // includes drawing the autocompletion menu, observing keyboard | ||
17 | // and mouse events, and similar. | ||
18 | // | ||
19 | // Specific autocompleters need to provide, at the very least, | ||
20 | // a getUpdatedChoices function that will be invoked every time | ||
21 | // the text inside the monitored textbox changes. This method | ||
22 | // should get the text for which to provide autocompletion by | ||
23 | // invoking this.getToken(), NOT by directly accessing | ||
24 | // this.element.value. This is to allow incremental tokenized | ||
25 | // autocompletion. Specific auto-completion logic (AJAX, etc) | ||
26 | // belongs in getUpdatedChoices. | ||
27 | // | ||
28 | // Tokenized incremental autocompletion is enabled automatically | ||
29 | // when an autocompleter is instantiated with the 'tokens' option | ||
30 | // in the options parameter, e.g.: | ||
31 | // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); | ||
32 | // will incrementally autocomplete with a comma as the token. | ||
33 | // Additionally, ',' in the above example can be replaced with | ||
34 | // a token array, e.g. { tokens: [',', '\n'] } which | ||
35 | // enables autocompletion on multiple tokens. This is most | ||
36 | // useful when one of the tokens is \n (a newline), as it | ||
37 | // allows smart autocompletion after linebreaks. | ||
38 | |||
39 | if(typeof Effect == 'undefined') | ||
40 | throw("controls.js requires including script.aculo.us' effects.js library"); | ||
41 | |||
42 | var Autocompleter = {} | ||
43 | Autocompleter.Base = function() {}; | ||
44 | Autocompleter.Base.prototype = { | ||
45 | baseInitialize: function(element, update, options) { | ||
46 | element = $(element) | ||
47 | this.element = element; | ||
48 | this.update = $(update); | ||
49 | this.hasFocus = false; | ||
50 | this.changed = false; | ||
51 | this.active = false; | ||
52 | this.index = 0; | ||
53 | this.entryCount = 0; | ||
54 | |||
55 | if(this.setOptions) | ||
56 | this.setOptions(options); | ||
57 | else | ||
58 | this.options = options || {}; | ||
59 | |||
60 | this.options.paramName = this.options.paramName || this.element.name; | ||
61 | this.options.tokens = this.options.tokens || []; | ||
62 | this.options.frequency = this.options.frequency || 0.4; | ||
63 | this.options.minChars = this.options.minChars || 1; | ||
64 | this.options.onShow = this.options.onShow || | ||
65 | function(element, update){ | ||
66 | if(!update.style.position || update.style.position=='absolute') { | ||
67 | update.style.position = 'absolute'; | ||
68 | Position.clone(element, update, { | ||
69 | setHeight: false, | ||
70 | offsetTop: element.offsetHeight | ||
71 | }); | ||
72 | } | ||
73 | Effect.Appear(update,{duration:0.15}); | ||
74 | }; | ||
75 | this.options.onHide = this.options.onHide || | ||
76 | function(element, update){ new Effect.Fade(update,{duration:0.15}) }; | ||
77 | |||
78 | if(typeof(this.options.tokens) == 'string') | ||
79 | this.options.tokens = new Array(this.options.tokens); | ||
80 | |||
81 | this.observer = null; | ||
82 | |||
83 | this.element.setAttribute('autocomplete','off'); | ||
84 | |||
85 | Element.hide(this.update); | ||
86 | |||
87 | Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); | ||
88 | Event.observe(this.element, 'keypress', this.onKeyPress.bindAsEventListener(this)); | ||
89 | |||
90 | // Turn autocomplete back on when the user leaves the page, so that the | ||
91 | // field's value will be remembered on Mozilla-based browsers. | ||
92 | Event.observe(window, 'beforeunload', function(){ | ||
93 | element.setAttribute('autocomplete', 'on'); | ||
94 | }); | ||
95 | }, | ||
96 | |||
97 | show: function() { | ||
98 | if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); | ||
99 | if(!this.iefix && | ||
100 | (Prototype.Browser.IE) && | ||
101 | (Element.getStyle(this.update, 'position')=='absolute')) { | ||
102 | new Insertion.After(this.update, | ||
103 | '<iframe id="' + this.update.id + '_iefix" '+ | ||
104 | 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + | ||
105 | 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); | ||
106 | this.iefix = $(this.update.id+'_iefix'); | ||
107 | } | ||
108 | if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); | ||
109 | }, | ||
110 | |||
111 | fixIEOverlapping: function() { | ||
112 | Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); | ||
113 | this.iefix.style.zIndex = 1; | ||
114 | this.update.style.zIndex = 2; | ||
115 | Element.show(this.iefix); | ||
116 | }, | ||
117 | |||
118 | hide: function() { | ||
119 | this.stopIndicator(); | ||
120 | if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); | ||
121 | if(this.iefix) Element.hide(this.iefix); | ||
122 | }, | ||
123 | |||
124 | startIndicator: function() { | ||
125 | if(this.options.indicator) Element.show(this.options.indicator); | ||
126 | }, | ||
127 | |||
128 | stopIndicator: function() { | ||
129 | if(this.options.indicator) Element.hide(this.options.indicator); | ||
130 | }, | ||
131 | |||
132 | onKeyPress: function(event) { | ||
133 | if(this.active) | ||
134 | switch(event.keyCode) { | ||
135 | case Event.KEY_TAB: | ||
136 | case Event.KEY_RETURN: | ||
137 | this.selectEntry(); | ||
138 | Event.stop(event); | ||
139 | case Event.KEY_ESC: | ||
140 | this.hide(); | ||
141 | this.active = false; | ||
142 | Event.stop(event); | ||
143 | return; | ||
144 | case Event.KEY_LEFT: | ||
145 | case Event.KEY_RIGHT: | ||
146 | return; | ||
147 | case Event.KEY_UP: | ||
148 | this.markPrevious(); | ||
149 | this.render(); | ||
150 | if(Prototype.Browser.WebKit) Event.stop(event); | ||
151 | return; | ||
152 | case Event.KEY_DOWN: | ||
153 | this.markNext(); | ||
154 | this.render(); | ||
155 | if(Prototype.Browser.WebKit) Event.stop(event); | ||
156 | return; | ||
157 | } | ||
158 | else | ||
159 | if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || | ||
160 | (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; | ||
161 | |||
162 | this.changed = true; | ||
163 | this.hasFocus = true; | ||
164 | |||
165 | if(this.observer) clearTimeout(this.observer); | ||
166 | this.observer = | ||
167 | setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); | ||
168 | }, | ||
169 | |||
170 | activate: function() { | ||
171 | this.changed = false; | ||
172 | this.hasFocus = true; | ||
173 | this.getUpdatedChoices(); | ||
174 | }, | ||
175 | |||
176 | onHover: function(event) { | ||
177 | var element = Event.findElement(event, 'LI'); | ||
178 | if(this.index != element.autocompleteIndex) | ||
179 | { | ||
180 | this.index = element.autocompleteIndex; | ||
181 | this.render(); | ||
182 | } | ||
183 | Event.stop(event); | ||
184 | }, | ||
185 | |||
186 | onClick: function(event) { | ||
187 | var element = Event.findElement(event, 'LI'); | ||
188 | this.index = element.autocompleteIndex; | ||
189 | this.selectEntry(); | ||
190 | this.hide(); | ||
191 | }, | ||
192 | |||
193 | onBlur: function(event) { | ||
194 | // needed to make click events working | ||
195 | setTimeout(this.hide.bind(this), 250); | ||
196 | this.hasFocus = false; | ||
197 | this.active = false; | ||
198 | }, | ||
199 | |||
200 | render: function() { | ||
201 | if(this.entryCount > 0) { | ||
202 | for (var i = 0; i < this.entryCount; i++) | ||
203 | this.index==i ? | ||
204 | Element.addClassName(this.getEntry(i),"selected") : | ||
205 | Element.removeClassName(this.getEntry(i),"selected"); | ||
206 | if(this.hasFocus) { | ||
207 | this.show(); | ||
208 | this.active = true; | ||
209 | } | ||
210 | } else { | ||
211 | this.active = false; | ||
212 | this.hide(); | ||
213 | } | ||
214 | }, | ||
215 | |||
216 | markPrevious: function() { | ||
217 | if(this.index > 0) this.index-- | ||
218 | else this.index = this.entryCount-1; | ||
219 | this.getEntry(this.index).scrollIntoView(true); | ||
220 | }, | ||
221 | |||
222 | markNext: function() { | ||
223 | if(this.index < this.entryCount-1) this.index++ | ||
224 | else this.index = 0; | ||
225 | this.getEntry(this.index).scrollIntoView(false); | ||
226 | }, | ||
227 | |||
228 | getEntry: function(index) { | ||
229 | return this.update.firstChild.childNodes[index]; | ||
230 | }, | ||
231 | |||
232 | getCurrentEntry: function() { | ||
233 | return this.getEntry(this.index); | ||
234 | }, | ||
235 | |||
236 | selectEntry: function() { | ||
237 | this.active = false; | ||
238 | this.updateElement(this.getCurrentEntry()); | ||
239 | }, | ||
240 | |||
241 | updateElement: function(selectedElement) { | ||
242 | if (this.options.updateElement) { | ||
243 | this.options.updateElement(selectedElement); | ||
244 | return; | ||
245 | } | ||
246 | var value = ''; | ||
247 | if (this.options.select) { | ||
248 | var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; | ||
249 | if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); | ||
250 | } else | ||
251 | value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); | ||
252 | |||
253 | var lastTokenPos = this.findLastToken(); | ||
254 | if (lastTokenPos != -1) { | ||
255 | var newValue = this.element.value.substr(0, lastTokenPos + 1); | ||
256 | var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); | ||
257 | if (whitespace) | ||
258 | newValue += whitespace[0]; | ||
259 | this.element.value = newValue + value; | ||
260 | } else { | ||
261 | this.element.value = value; | ||
262 | } | ||
263 | this.element.focus(); | ||
264 | |||
265 | if (this.options.afterUpdateElement) | ||
266 | this.options.afterUpdateElement(this.element, selectedElement); | ||
267 | }, | ||
268 | |||
269 | updateChoices: function(choices) { | ||
270 | if(!this.changed && this.hasFocus) { | ||
271 | this.update.innerHTML = choices; | ||
272 | Element.cleanWhitespace(this.update); | ||
273 | Element.cleanWhitespace(this.update.down()); | ||
274 | |||
275 | if(this.update.firstChild && this.update.down().childNodes) { | ||
276 | this.entryCount = | ||
277 | this.update.down().childNodes.length; | ||
278 | for (var i = 0; i < this.entryCount; i++) { | ||
279 | var entry = this.getEntry(i); | ||
280 | entry.autocompleteIndex = i; | ||
281 | this.addObservers(entry); | ||
282 | } | ||
283 | } else { | ||
284 | this.entryCount = 0; | ||
285 | } | ||
286 | |||
287 | this.stopIndicator(); | ||
288 | this.index = 0; | ||
289 | |||
290 | if(this.entryCount==1 && this.options.autoSelect) { | ||
291 | this.selectEntry(); | ||
292 | this.hide(); | ||
293 | } else { | ||
294 | this.render(); | ||
295 | } | ||
296 | } | ||
297 | }, | ||
298 | |||
299 | addObservers: function(element) { | ||
300 | Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); | ||
301 | Event.observe(element, "click", this.onClick.bindAsEventListener(this)); | ||
302 | }, | ||
303 | |||
304 | onObserverEvent: function() { | ||
305 | this.changed = false; | ||
306 | if(this.getToken().length>=this.options.minChars) { | ||
307 | this.getUpdatedChoices(); | ||
308 | } else { | ||
309 | this.active = false; | ||
310 | this.hide(); | ||
311 | } | ||
312 | }, | ||
313 | |||
314 | getToken: function() { | ||
315 | var tokenPos = this.findLastToken(); | ||
316 | if (tokenPos != -1) | ||
317 | var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); | ||
318 | else | ||
319 | var ret = this.element.value; | ||
320 | |||
321 | return /\n/.test(ret) ? '' : ret; | ||
322 | }, | ||
323 | |||
324 | findLastToken: function() { | ||
325 | var lastTokenPos = -1; | ||
326 | |||
327 | for (var i=0; i<this.options.tokens.length; i++) { | ||
328 | var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]); | ||
329 | if (thisTokenPos > lastTokenPos) | ||
330 | lastTokenPos = thisTokenPos; | ||
331 | } | ||
332 | return lastTokenPos; | ||
333 | } | ||
334 | } | ||
335 | |||
336 | Ajax.Autocompleter = Class.create(); | ||
337 | Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { | ||
338 | initialize: function(element, update, url, options) { | ||
339 | this.baseInitialize(element, update, options); | ||
340 | this.options.asynchronous = true; | ||
341 | this.options.onComplete = this.onComplete.bind(this); | ||
342 | this.options.defaultParams = this.options.parameters || null; | ||
343 | this.url = url; | ||
344 | }, | ||
345 | |||
346 | getUpdatedChoices: function() { | ||
347 | this.startIndicator(); | ||
348 | |||
349 | var entry = encodeURIComponent(this.options.paramName) + '=' + | ||
350 | encodeURIComponent(this.getToken()); | ||
351 | |||
352 | this.options.parameters = this.options.callback ? | ||
353 | this.options.callback(this.element, entry) : entry; | ||
354 | |||
355 | if(this.options.defaultParams) | ||
356 | this.options.parameters += '&' + this.options.defaultParams; | ||
357 | |||
358 | new Ajax.Request(this.url, this.options); | ||
359 | }, | ||
360 | |||
361 | onComplete: function(request) { | ||
362 | this.updateChoices(request.responseText); | ||
363 | } | ||
364 | |||
365 | }); | ||
366 | |||
367 | // The local array autocompleter. Used when you'd prefer to | ||
368 | // inject an array of autocompletion options into the page, rather | ||
369 | // than sending out Ajax queries, which can be quite slow sometimes. | ||
370 | // | ||
371 | // The constructor takes four parameters. The first two are, as usual, | ||
372 | // the id of the monitored textbox, and id of the autocompletion menu. | ||
373 | // The third is the array you want to autocomplete from, and the fourth | ||
374 | // is the options block. | ||
375 | // | ||
376 | // Extra local autocompletion options: | ||
377 | // - choices - How many autocompletion choices to offer | ||
378 | // | ||
379 | // - partialSearch - If false, the autocompleter will match entered | ||
380 | // text only at the beginning of strings in the | ||
381 | // autocomplete array. Defaults to true, which will | ||
382 | // match text at the beginning of any *word* in the | ||
383 | // strings in the autocomplete array. If you want to | ||
384 | // search anywhere in the string, additionally set | ||
385 | // the option fullSearch to true (default: off). | ||
386 | // | ||
387 | // - fullSsearch - Search anywhere in autocomplete array strings. | ||
388 | // | ||
389 | // - partialChars - How many characters to enter before triggering | ||
390 | // a partial match (unlike minChars, which defines | ||
391 | // how many characters are required to do any match | ||
392 | // at all). Defaults to 2. | ||
393 | // | ||
394 | // - ignoreCase - Whether to ignore case when autocompleting. | ||
395 | // Defaults to true. | ||
396 | // | ||
397 | // It's possible to pass in a custom function as the 'selector' | ||
398 | // option, if you prefer to write your own autocompletion logic. | ||
399 | // In that case, the other options above will not apply unless | ||
400 | // you support them. | ||
401 | |||
402 | Autocompleter.Local = Class.create(); | ||
403 | Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { | ||
404 | initialize: function(element, update, array, options) { | ||
405 | this.baseInitialize(element, update, options); | ||
406 | this.options.array = array; | ||
407 | }, | ||
408 | |||
409 | getUpdatedChoices: function() { | ||
410 | this.updateChoices(this.options.selector(this)); | ||
411 | }, | ||
412 | |||
413 | setOptions: function(options) { | ||
414 | this.options = Object.extend({ | ||
415 | choices: 10, | ||
416 | partialSearch: true, | ||
417 | partialChars: 2, | ||
418 | ignoreCase: true, | ||
419 | fullSearch: false, | ||
420 | selector: function(instance) { | ||
421 | var ret = []; // Beginning matches | ||
422 | var partial = []; // Inside matches | ||
423 | var entry = instance.getToken(); | ||
424 | var count = 0; | ||
425 | |||
426 | for (var i = 0; i < instance.options.array.length && | ||
427 | ret.length < instance.options.choices ; i++) { | ||
428 | |||
429 | var elem = instance.options.array[i]; | ||
430 | var foundPos = instance.options.ignoreCase ? | ||
431 | elem.toLowerCase().indexOf(entry.toLowerCase()) : | ||
432 | elem.indexOf(entry); | ||
433 | |||
434 | while (foundPos != -1) { | ||
435 | if (foundPos == 0 && elem.length != entry.length) { | ||
436 | ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + | ||
437 | elem.substr(entry.length) + "</li>"); | ||
438 | break; | ||
439 | } else if (entry.length >= instance.options.partialChars && | ||
440 | instance.options.partialSearch && foundPos != -1) { | ||
441 | if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { | ||
442 | partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" + | ||
443 | elem.substr(foundPos, entry.length) + "</strong>" + elem.substr( | ||
444 | foundPos + entry.length) + "</li>"); | ||
445 | break; | ||
446 | } | ||
447 | } | ||
448 | |||
449 | foundPos = instance.options.ignoreCase ? | ||
450 | elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : | ||
451 | elem.indexOf(entry, foundPos + 1); | ||
452 | |||
453 | } | ||
454 | } | ||
455 | if (partial.length) | ||
456 | ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) | ||
457 | return "<ul>" + ret.join('') + "</ul>"; | ||
458 | } | ||
459 | }, options || {}); | ||
460 | } | ||
461 | }); | ||
462 | |||
463 | // AJAX in-place editor | ||
464 | // | ||
465 | // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor | ||
466 | |||
467 | // Use this if you notice weird scrolling problems on some browsers, | ||
468 | // the DOM might be a bit confused when this gets called so do this | ||
469 | // waits 1 ms (with setTimeout) until it does the activation | ||
470 | Field.scrollFreeActivate = function(field) { | ||
471 | setTimeout(function() { | ||
472 | Field.activate(field); | ||
473 | }, 1); | ||
474 | } | ||
475 | |||
476 | Ajax.InPlaceEditor = Class.create(); | ||
477 | Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; | ||
478 | Ajax.InPlaceEditor.prototype = { | ||
479 | initialize: function(element, url, options) { | ||
480 | this.url = url; | ||
481 | this.element = $(element); | ||
482 | |||
483 | this.options = Object.extend({ | ||
484 | paramName: "value", | ||
485 | okButton: true, | ||
486 | okLink: false, | ||
487 | okText: "ok", | ||
488 | cancelButton: false, | ||
489 | cancelLink: true, | ||
490 | cancelText: "cancel", | ||
491 | textBeforeControls: '', | ||
492 | textBetweenControls: '', | ||
493 | textAfterControls: '', | ||
494 | savingText: "Saving...", | ||
495 | clickToEditText: "Click to edit", | ||
496 | okText: "ok", | ||
497 | rows: 1, | ||
498 | onComplete: function(transport, element) { | ||
499 | new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); | ||
500 | }, | ||
501 | onFailure: function(transport) { | ||
502 | alert("Error communicating with the server: " + transport.responseText.stripTags()); | ||
503 | }, | ||
504 | callback: function(form) { | ||
505 | return Form.serialize(form); | ||
506 | }, | ||
507 | handleLineBreaks: true, | ||
508 | loadingText: 'Loading...', | ||
509 | savingClassName: 'inplaceeditor-saving', | ||
510 | loadingClassName: 'inplaceeditor-loading', | ||
511 | formClassName: 'inplaceeditor-form', | ||
512 | highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, | ||
513 | highlightendcolor: "#FFFFFF", | ||
514 | externalControl: null, | ||
515 | submitOnBlur: false, | ||
516 | ajaxOptions: {}, | ||
517 | evalScripts: false | ||
518 | }, options || {}); | ||
519 | |||
520 | if(!this.options.formId && this.element.id) { | ||
521 | this.options.formId = this.element.id + "-inplaceeditor"; | ||
522 | if ($(this.options.formId)) { | ||
523 | // there's already a form with that name, don't specify an id | ||
524 | this.options.formId = null; | ||
525 | } | ||
526 | } | ||
527 | |||
528 | if (this.options.externalControl) { | ||
529 | this.options.externalControl = $(this.options.externalControl); | ||
530 | } | ||
531 | |||
532 | this.originalBackground = Element.getStyle(this.element, 'background-color'); | ||
533 | if (!this.originalBackground) { | ||
534 | this.originalBackground = "transparent"; | ||
535 | } | ||
536 | |||
537 | this.element.title = this.options.clickToEditText; | ||
538 | |||
539 | this.onclickListener = this.enterEditMode.bindAsEventListener(this); | ||
540 | this.mouseoverListener = this.enterHover.bindAsEventListener(this); | ||
541 | this.mouseoutListener = this.leaveHover.bindAsEventListener(this); | ||
542 | Event.observe(this.element, 'click', this.onclickListener); | ||
543 | Event.observe(this.element, 'mouseover', this.mouseoverListener); | ||
544 | Event.observe(this.element, 'mouseout', this.mouseoutListener); | ||
545 | if (this.options.externalControl) { | ||
546 | Event.observe(this.options.externalControl, 'click', this.onclickListener); | ||
547 | Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); | ||
548 | Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); | ||
549 | } | ||
550 | }, | ||
551 | enterEditMode: function(evt) { | ||
552 | if (this.saving) return; | ||
553 | if (this.editing) return; | ||
554 | this.editing = true; | ||
555 | this.onEnterEditMode(); | ||
556 | if (this.options.externalControl) { | ||
557 | Element.hide(this.options.externalControl); | ||
558 | } | ||
559 | Element.hide(this.element); | ||
560 | this.createForm(); | ||
561 | this.element.parentNode.insertBefore(this.form, this.element); | ||
562 | if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField); | ||
563 | // stop the event to avoid a page refresh in Safari | ||
564 | if (evt) { | ||
565 | Event.stop(evt); | ||
566 | } | ||
567 | return false; | ||
568 | }, | ||
569 | createForm: function() { | ||
570 | this.form = document.createElement("form"); | ||
571 | this.form.id = this.options.formId; | ||
572 | Element.addClassName(this.form, this.options.formClassName) | ||
573 | this.form.onsubmit = this.onSubmit.bind(this); | ||
574 | |||
575 | this.createEditField(); | ||
576 | |||
577 | if (this.options.textarea) { | ||
578 | var br = document.createElement("br"); | ||
579 | this.form.appendChild(br); | ||
580 | } | ||
581 | |||
582 | if (this.options.textBeforeControls) | ||
583 | this.form.appendChild(document.createTextNode(this.options.textBeforeControls)); | ||
584 | |||
585 | if (this.options.okButton) { | ||
586 | var okButton = document.createElement("input"); | ||
587 | okButton.type = "submit"; | ||
588 | okButton.value = this.options.okText; | ||
589 | okButton.className = 'editor_ok_button'; | ||
590 | this.form.appendChild(okButton); | ||
591 | } | ||
592 | |||
593 | if (this.options.okLink) { | ||
594 | var okLink = document.createElement("a"); | ||
595 | okLink.href = "#"; | ||
596 | okLink.appendChild(document.createTextNode(this.options.okText)); | ||
597 | okLink.onclick = this.onSubmit.bind(this); | ||
598 | okLink.className = 'editor_ok_link'; | ||
599 | this.form.appendChild(okLink); | ||
600 | } | ||
601 | |||
602 | if (this.options.textBetweenControls && | ||
603 | (this.options.okLink || this.options.okButton) && | ||
604 | (this.options.cancelLink || this.options.cancelButton)) | ||
605 | this.form.appendChild(document.createTextNode(this.options.textBetweenControls)); | ||
606 | |||
607 | if (this.options.cancelButton) { | ||
608 | var cancelButton = document.createElement("input"); | ||
609 | cancelButton.type = "submit"; | ||
610 | cancelButton.value = this.options.cancelText; | ||
611 | cancelButton.onclick = this.onclickCancel.bind(this); | ||
612 | cancelButton.className = 'editor_cancel_button'; | ||
613 | this.form.appendChild(cancelButton); | ||
614 | } | ||
615 | |||
616 | if (this.options.cancelLink) { | ||
617 | var cancelLink = document.createElement("a"); | ||
618 | cancelLink.href = "#"; | ||
619 | cancelLink.appendChild(document.createTextNode(this.options.cancelText)); | ||
620 | cancelLink.onclick = this.onclickCancel.bind(this); | ||
621 | cancelLink.className = 'editor_cancel editor_cancel_link'; | ||
622 | this.form.appendChild(cancelLink); | ||
623 | } | ||
624 | |||
625 | if (this.options.textAfterControls) | ||
626 | this.form.appendChild(document.createTextNode(this.options.textAfterControls)); | ||
627 | }, | ||
628 | hasHTMLLineBreaks: function(string) { | ||
629 | if (!this.options.handleLineBreaks) return false; | ||
630 | return string.match(/<br/i) || string.match(/<p>/i); | ||
631 | }, | ||
632 | convertHTMLLineBreaks: function(string) { | ||
633 | return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, ""); | ||
634 | }, | ||
635 | createEditField: function() { | ||
636 | var text; | ||
637 | if(this.options.loadTextURL) { | ||
638 | text = this.options.loadingText; | ||
639 | } else { | ||
640 | text = this.getText(); | ||
641 | } | ||
642 | |||
643 | var obj = this; | ||
644 | |||
645 | if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { | ||
646 | this.options.textarea = false; | ||
647 | var textField = document.createElement("input"); | ||
648 | textField.obj = this; | ||
649 | textField.type = "text"; | ||
650 | textField.name = this.options.paramName; | ||
651 | textField.value = text; | ||
652 | textField.style.backgroundColor = this.options.highlightcolor; | ||
653 | textField.className = 'editor_field'; | ||
654 | var size = this.options.size || this.options.cols || 0; | ||
655 | if (size != 0) textField.size = size; | ||
656 | if (this.options.submitOnBlur) | ||
657 | textField.onblur = this.onSubmit.bind(this); | ||
658 | this.editField = textField; | ||
659 | } else { | ||
660 | this.options.textarea = true; | ||
661 | var textArea = document.createElement("textarea"); | ||
662 | textArea.obj = this; | ||
663 | textArea.name = this.options.paramName; | ||
664 | textArea.value = this.convertHTMLLineBreaks(text); | ||
665 | textArea.rows = this.options.rows; | ||
666 | textArea.cols = this.options.cols || 40; | ||
667 | textArea.className = 'editor_field'; | ||
668 | if (this.options.submitOnBlur) | ||
669 | textArea.onblur = this.onSubmit.bind(this); | ||
670 | this.editField = textArea; | ||
671 | } | ||
672 | |||
673 | if(this.options.loadTextURL) { | ||
674 | this.loadExternalText(); | ||
675 | } | ||
676 | this.form.appendChild(this.editField); | ||
677 | }, | ||
678 | getText: function() { | ||
679 | return this.element.innerHTML; | ||
680 | }, | ||
681 | loadExternalText: function() { | ||
682 | Element.addClassName(this.form, this.options.loadingClassName); | ||
683 | this.editField.disabled = true; | ||
684 | new Ajax.Request( | ||
685 | this.options.loadTextURL, | ||
686 | Object.extend({ | ||
687 | asynchronous: true, | ||
688 | onComplete: this.onLoadedExternalText.bind(this) | ||
689 | }, this.options.ajaxOptions) | ||
690 | ); | ||
691 | }, | ||
692 | onLoadedExternalText: function(transport) { | ||
693 | Element.removeClassName(this.form, this.options.loadingClassName); | ||
694 | this.editField.disabled = false; | ||
695 | this.editField.value = transport.responseText.stripTags(); | ||
696 | Field.scrollFreeActivate(this.editField); | ||
697 | }, | ||
698 | onclickCancel: function() { | ||
699 | this.onComplete(); | ||
700 | this.leaveEditMode(); | ||
701 | return false; | ||
702 | }, | ||
703 | onFailure: function(transport) { | ||
704 | this.options.onFailure(transport); | ||
705 | if (this.oldInnerHTML) { | ||
706 | this.element.innerHTML = this.oldInnerHTML; | ||
707 | this.oldInnerHTML = null; | ||
708 | } | ||
709 | return false; | ||
710 | }, | ||
711 | onSubmit: function() { | ||
712 | // onLoading resets these so we need to save them away for the Ajax call | ||
713 | var form = this.form; | ||
714 | var value = this.editField.value; | ||
715 | |||
716 | // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... | ||
717 | // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... | ||
718 | // to be displayed indefinitely | ||
719 | this.onLoading(); | ||
720 | |||
721 | if (this.options.evalScripts) { | ||
722 | new Ajax.Request( | ||
723 | this.url, Object.extend({ | ||
724 | parameters: this.options.callback(form, value), | ||
725 | onComplete: this.onComplete.bind(this), | ||
726 | onFailure: this.onFailure.bind(this), | ||
727 | asynchronous:true, | ||
728 | evalScripts:true | ||
729 | }, this.options.ajaxOptions)); | ||
730 | } else { | ||
731 | new Ajax.Updater( | ||
732 | { success: this.element, | ||
733 | // don't update on failure (this could be an option) | ||
734 | failure: null }, | ||
735 | this.url, Object.extend({ | ||
736 | parameters: this.options.callback(form, value), | ||
737 | onComplete: this.onComplete.bind(this), | ||
738 | onFailure: this.onFailure.bind(this) | ||
739 | }, this.options.ajaxOptions)); | ||
740 | } | ||
741 | // stop the event to avoid a page refresh in Safari | ||
742 | if (arguments.length > 1) { | ||
743 | Event.stop(arguments[0]); | ||
744 | } | ||
745 | return false; | ||
746 | }, | ||
747 | onLoading: function() { | ||
748 | this.saving = true; | ||
749 | this.removeForm(); | ||
750 | this.leaveHover(); | ||
751 | this.showSaving(); | ||
752 | }, | ||
753 | showSaving: function() { | ||
754 | this.oldInnerHTML = this.element.innerHTML; | ||
755 | this.element.innerHTML = this.options.savingText; | ||
756 | Element.addClassName(this.element, this.options.savingClassName); | ||
757 | this.element.style.backgroundColor = this.originalBackground; | ||
758 | Element.show(this.element); | ||
759 | }, | ||
760 | removeForm: function() { | ||
761 | if(this.form) { | ||
762 | if (this.form.parentNode) Element.remove(this.form); | ||
763 | this.form = null; | ||
764 | } | ||
765 | }, | ||
766 | enterHover: function() { | ||
767 | if (this.saving) return; | ||
768 | this.element.style.backgroundColor = this.options.highlightcolor; | ||
769 | if (this.effect) { | ||
770 | this.effect.cancel(); | ||
771 | } | ||
772 | Element.addClassName(this.element, this.options.hoverClassName) | ||
773 | }, | ||
774 | leaveHover: function() { | ||
775 | if (this.options.backgroundColor) { | ||
776 | this.element.style.backgroundColor = this.oldBackground; | ||
777 | } | ||
778 | Element.removeClassName(this.element, this.options.hoverClassName) | ||
779 | if (this.saving) return; | ||
780 | this.effect = new Effect.Highlight(this.element, { | ||
781 | startcolor: this.options.highlightcolor, | ||
782 | endcolor: this.options.highlightendcolor, | ||
783 | restorecolor: this.originalBackground | ||
784 | }); | ||
785 | }, | ||
786 | leaveEditMode: function() { | ||
787 | Element.removeClassName(this.element, this.options.savingClassName); | ||
788 | this.removeForm(); | ||
789 | this.leaveHover(); | ||
790 | this.element.style.backgroundColor = this.originalBackground; | ||
791 | Element.show(this.element); | ||
792 | if (this.options.externalControl) { | ||
793 | Element.show(this.options.externalControl); | ||
794 | } | ||
795 | this.editing = false; | ||
796 | this.saving = false; | ||
797 | this.oldInnerHTML = null; | ||
798 | this.onLeaveEditMode(); | ||
799 | }, | ||
800 | onComplete: function(transport) { | ||
801 | this.leaveEditMode(); | ||
802 | this.options.onComplete.bind(this)(transport, this.element); | ||
803 | }, | ||
804 | onEnterEditMode: function() {}, | ||
805 | onLeaveEditMode: function() {}, | ||
806 | dispose: function() { | ||
807 | if (this.oldInnerHTML) { | ||
808 | this.element.innerHTML = this.oldInnerHTML; | ||
809 | } | ||
810 | this.leaveEditMode(); | ||
811 | Event.stopObserving(this.element, 'click', this.onclickListener); | ||
812 | Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); | ||
813 | Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); | ||
814 | if (this.options.externalControl) { | ||
815 | Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); | ||
816 | Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); | ||
817 | Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); | ||
818 | } | ||
819 | } | ||
820 | }; | ||
821 | |||
822 | Ajax.InPlaceCollectionEditor = Class.create(); | ||
823 | Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); | ||
824 | Object.extend(Ajax.InPlaceCollectionEditor.prototype, { | ||
825 | createEditField: function() { | ||
826 | if (!this.cached_selectTag) { | ||
827 | var selectTag = document.createElement("select"); | ||
828 | var collection = this.options.collection || []; | ||
829 | var optionTag; | ||
830 | collection.each(function(e,i) { | ||
831 | optionTag = document.createElement("option"); | ||
832 | optionTag.value = (e instanceof Array) ? e[0] : e; | ||
833 | if((typeof this.options.value == 'undefined') && | ||
834 | ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true; | ||
835 | if(this.options.value==optionTag.value) optionTag.selected = true; | ||
836 | optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); | ||
837 | selectTag.appendChild(optionTag); | ||
838 | }.bind(this)); | ||
839 | this.cached_selectTag = selectTag; | ||
840 | } | ||
841 | |||
842 | this.editField = this.cached_selectTag; | ||
843 | if(this.options.loadTextURL) this.loadExternalText(); | ||
844 | this.form.appendChild(this.editField); | ||
845 | this.options.callback = function(form, value) { | ||
846 | return "value=" + encodeURIComponent(value); | ||
847 | } | ||
848 | } | ||
849 | }); | ||
850 | |||
851 | // Delayed observer, like Form.Element.Observer, | ||
852 | // but waits for delay after last key input | ||
853 | // Ideal for live-search fields | ||
854 | |||
855 | Form.Element.DelayedObserver = Class.create(); | ||
856 | Form.Element.DelayedObserver.prototype = { | ||
857 | initialize: function(element, delay, callback) { | ||
858 | this.delay = delay || 0.5; | ||
859 | this.element = $(element); | ||
860 | this.callback = callback; | ||
861 | this.timer = null; | ||
862 | this.lastValue = $F(this.element); | ||
863 | Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); | ||
864 | }, | ||
865 | delayedListener: function(event) { | ||
866 | if(this.lastValue == $F(this.element)) return; | ||
867 | if(this.timer) clearTimeout(this.timer); | ||
868 | this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); | ||
869 | this.lastValue = $F(this.element); | ||
870 | }, | ||
871 | onTimerEvent: function() { | ||
872 | this.timer = null; | ||
873 | this.callback(this.element, $F(this.element)); | ||
874 | } | ||
875 | }; | ||