Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 86
- Log:
Initial import of I2, an Instiki clone.
- Author:
- adh
- Date:
- Mon Oct 16 10:40:36 +0100 2006
- Size:
- 18499 Bytes
1 | // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) |
2 | // |
3 | // Element.Class part Copyright (c) 2005 by Rick Olson |
4 | // |
5 | // Permission is hereby granted, free of charge, to any person obtaining |
6 | // a copy of this software and associated documentation files (the |
7 | // "Software"), to deal in the Software without restriction, including |
8 | // without limitation the rights to use, copy, modify, merge, publish, |
9 | // distribute, sublicense, and/or sell copies of the Software, and to |
10 | // permit persons to whom the Software is furnished to do so, subject to |
11 | // the following conditions: |
12 | // |
13 | // The above copyright notice and this permission notice shall be |
14 | // included in all copies or substantial portions of the Software. |
15 | // |
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
23 | |
24 | Element.Class = { |
25 | // Element.toggleClass(element, className) toggles the class being on/off |
26 | // Element.toggleClass(element, className1, className2) toggles between both classes, |
27 | // defaulting to className1 if neither exist |
28 | toggle: function(element, className) { |
29 | if(Element.Class.has(element, className)) { |
30 | Element.Class.remove(element, className); |
31 | if(arguments.length == 3) Element.Class.add(element, arguments[2]); |
32 | } else { |
33 | Element.Class.add(element, className); |
34 | if(arguments.length == 3) Element.Class.remove(element, arguments[2]); |
35 | } |
36 | }, |
37 | |
38 | // gets space-delimited classnames of an element as an array |
39 | get: function(element) { |
40 | element = $(element); |
41 | return element.className.split(' '); |
42 | }, |
43 | |
44 | // functions adapted from original functions by Gavin Kistner |
45 | remove: function(element) { |
46 | element = $(element); |
47 | var regEx; |
48 | for(var i = 1; i < arguments.length; i++) { |
49 | regEx = new RegExp("^" + arguments[i] + "\\b\\s*|\\s*\\b" + arguments[i] + "\\b", 'g'); |
50 | element.className = element.className.replace(regEx, '') |
51 | } |
52 | }, |
53 | |
54 | add: function(element) { |
55 | element = $(element); |
56 | for(var i = 1; i < arguments.length; i++) { |
57 | Element.Class.remove(element, arguments[i]); |
58 | element.className += (element.className.length > 0 ? ' ' : '') + arguments[i]; |
59 | } |
60 | }, |
61 | |
62 | // returns true if all given classes exist in said element |
63 | has: function(element) { |
64 | element = $(element); |
65 | if(!element || !element.className) return false; |
66 | var regEx; |
67 | for(var i = 1; i < arguments.length; i++) { |
68 | regEx = new RegExp("\\b" + arguments[i] + "\\b"); |
69 | if(!regEx.test(element.className)) return false; |
70 | } |
71 | return true; |
72 | }, |
73 | |
74 | // expects arrays of strings and/or strings as optional paramters |
75 | // Element.Class.has_any(element, ['classA','classB','classC'], 'classD') |
76 | has_any: function(element) { |
77 | element = $(element); |
78 | if(!element || !element.className) return false; |
79 | var regEx; |
80 | for(var i = 1; i < arguments.length; i++) { |
81 | if((typeof arguments[i] == 'object') && |
82 | (arguments[i].constructor == Array)) { |
83 | for(var j = 0; j < arguments[i].length; j++) { |
84 | regEx = new RegExp("\\b" + arguments[i][j] + "\\b"); |
85 | if(regEx.test(element.className)) return true; |
86 | } |
87 | } else { |
88 | regEx = new RegExp("\\b" + arguments[i] + "\\b"); |
89 | if(regEx.test(element.className)) return true; |
90 | } |
91 | } |
92 | return false; |
93 | }, |
94 | |
95 | childrenWith: function(element, className) { |
96 | var children = $(element).getElementsByTagName('*'); |
97 | var elements = new Array(); |
98 | |
99 | for (var i = 0; i < children.length; i++) { |
100 | if (Element.Class.has(children[i], className)) { |
101 | elements.push(children[i]); |
102 | break; |
103 | } |
104 | } |
105 | |
106 | return elements; |
107 | } |
108 | } |
109 | |
110 | /*--------------------------------------------------------------------------*/ |
111 | |
112 | var Droppables = { |
113 | drops: false, |
114 | |
115 | remove: function(element) { |
116 | for(var i = 0; i < this.drops.length; i++) |
117 | if(this.drops[i].element == element) |
118 | this.drops.splice(i,1); |
119 | }, |
120 | |
121 | add: function(element) { |
122 | var element = $(element); |
123 | var options = Object.extend({ |
124 | greedy: true, |
125 | hoverclass: null |
126 | }, arguments[1] || {}); |
127 | |
128 | // cache containers |
129 | if(options.containment) { |
130 | options._containers = new Array(); |
131 | var containment = options.containment; |
132 | if((typeof containment == 'object') && |
133 | (containment.constructor == Array)) { |
134 | for(var i=0; i<containment.length; i++) |
135 | options._containers.push($(containment[i])); |
136 | } else { |
137 | options._containers.push($(containment)); |
138 | } |
139 | options._containers_length = |
140 | options._containers.length-1; |
141 | } |
142 | |
143 | Element.makePositioned(element); // fix IE |
144 | |
145 | options.element = element; |
146 | |
147 | // activate the droppable |
148 | if(!this.drops) this.drops = []; |
149 | this.drops.push(options); |
150 | }, |
151 | |
152 | is_contained: function(element, drop) { |
153 | var containers = drop._containers; |
154 | var parentNode = element.parentNode; |
155 | var i = drop._containers_length; |
156 | do { if(parentNode==containers[i]) return true; } while (i--); |
157 | return false; |
158 | }, |
159 | |
160 | is_affected: function(pX, pY, element, drop) { |
161 | return ( |
162 | (drop.element!=element) && |
163 | ((!drop._containers) || |
164 | this.is_contained(element, drop)) && |
165 | ((!drop.accept) || |
166 | (Element.Class.has_any(element, drop.accept))) && |
167 | Position.within(drop.element, pX, pY) ); |
168 | }, |
169 | |
170 | deactivate: function(drop) { |
171 | Element.Class.remove(drop.element, drop.hoverclass); |
172 | this.last_active = null; |
173 | }, |
174 | |
175 | activate: function(drop) { |
176 | if(this.last_active) this.deactivate(this.last_active); |
177 | if(drop.hoverclass) { |
178 | Element.Class.add(drop.element, drop.hoverclass); |
179 | this.last_active = drop; |
180 | } |
181 | }, |
182 | |
183 | show: function(event, element) { |
184 | if(!this.drops) return; |
185 | var pX = Event.pointerX(event); |
186 | var pY = Event.pointerY(event); |
187 | Position.prepare(); |
188 | |
189 | var i = this.drops.length-1; do { |
190 | var drop = this.drops[i]; |
191 | if(this.is_affected(pX, pY, element, drop)) { |
192 | if(drop.onHover) |
193 | drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); |
194 | if(drop.greedy) { |
195 | this.activate(drop); |
196 | return; |
197 | } |
198 | } |
199 | } while (i--); |
200 | }, |
201 | |
202 | fire: function(event, element) { |
203 | if(!this.last_active) return; |
204 | Position.prepare(); |
205 | |
206 | if (this.is_affected(Event.pointerX(event), Event.pointerY(event), element, this.last_active)) |
207 | if (this.last_active.onDrop) |
208 | this.last_active.onDrop(element, this.last_active); |
209 | |
210 | }, |
211 | |
212 | reset: function() { |
213 | if(this.last_active) |
214 | this.deactivate(this.last_active); |
215 | } |
216 | } |
217 | |
218 | Draggables = { |
219 | observers: new Array(), |
220 | addObserver: function(observer) { |
221 | this.observers.push(observer); |
222 | }, |
223 | removeObserver: function(element) { // element instead of obsever fixes mem leaks |
224 | for(var i = 0; i < this.observers.length; i++) |
225 | if(this.observers[i].element && (this.observers[i].element == element)) |
226 | this.observers.splice(i,1); |
227 | }, |
228 | notify: function(eventName, draggable) { // 'onStart', 'onEnd' |
229 | for(var i = 0; i < this.observers.length; i++) |
230 | this.observers[i][eventName](draggable); |
231 | } |
232 | } |
233 | |
234 | /*--------------------------------------------------------------------------*/ |
235 | |
236 | Draggable = Class.create(); |
237 | Draggable.prototype = { |
238 | initialize: function(element) { |
239 | var options = Object.extend({ |
240 | handle: false, |
241 | starteffect: function(element) { |
242 | new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); |
243 | }, |
244 | reverteffect: function(element, top_offset, left_offset) { |
245 | new Effect.MoveBy(element, -top_offset, -left_offset, {duration:0.4}); |
246 | }, |
247 | endeffect: function(element) { |
248 | new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); |
249 | }, |
250 | zindex: 1000, |
251 | revert: false |
252 | }, arguments[1] || {}); |
253 | |
254 | this.element = $(element); |
255 | this.handle = options.handle ? $(options.handle) : this.element; |
256 | |
257 | Element.makePositioned(this.element); // fix IE |
258 | |
259 | this.offsetX = 0; |
260 | this.offsetY = 0; |
261 | this.originalLeft = this.currentLeft(); |
262 | this.originalTop = this.currentTop(); |
263 | this.originalX = this.element.offsetLeft; |
264 | this.originalY = this.element.offsetTop; |
265 | this.originalZ = parseInt(this.element.style.zIndex || "0"); |
266 | |
267 | this.options = options; |
268 | |
269 | this.active = false; |
270 | this.dragging = false; |
271 | |
272 | this.eventMouseDown = this.startDrag.bindAsEventListener(this); |
273 | this.eventMouseUp = this.endDrag.bindAsEventListener(this); |
274 | this.eventMouseMove = this.update.bindAsEventListener(this); |
275 | this.eventKeypress = this.keyPress.bindAsEventListener(this); |
276 | |
277 | Event.observe(this.handle, "mousedown", this.eventMouseDown); |
278 | Event.observe(document, "mouseup", this.eventMouseUp); |
279 | Event.observe(document, "mousemove", this.eventMouseMove); |
280 | Event.observe(document, "keypress", this.eventKeypress); |
281 | }, |
282 | destroy: function() { |
283 | Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); |
284 | Event.stopObserving(document, "mouseup", this.eventMouseUp); |
285 | Event.stopObserving(document, "mousemove", this.eventMouseMove); |
286 | Event.stopObserving(document, "keypress", this.eventKeypress); |
287 | }, |
288 | currentLeft: function() { |
289 | return parseInt(this.element.style.left || '0'); |
290 | }, |
291 | currentTop: function() { |
292 | return parseInt(this.element.style.top || '0') |
293 | }, |
294 | startDrag: function(event) { |
295 | if(Event.isLeftClick(event)) { |
296 | this.active = true; |
297 | |
298 | var style = this.element.style; |
299 | this.originalY = this.element.offsetTop - this.currentTop() - this.originalTop; |
300 | this.originalX = this.element.offsetLeft - this.currentLeft() - this.originalLeft; |
301 | this.offsetY = event.clientY - this.originalY - this.originalTop; |
302 | this.offsetX = event.clientX - this.originalX - this.originalLeft; |
303 | |
304 | Event.stop(event); |
305 | } |
306 | }, |
307 | finishDrag: function(event, success) { |
308 | this.active = false; |
309 | this.dragging = false; |
310 | |
311 | if(success) Droppables.fire(event, this.element); |
312 | Draggables.notify('onEnd', this); |
313 | |
314 | var revert = this.options.revert; |
315 | if(revert && typeof revert == 'function') revert = revert(this.element); |
316 | |
317 | if(revert && this.options.reverteffect) { |
318 | this.options.reverteffect(this.element, |
319 | this.currentTop()-this.originalTop, |
320 | this.currentLeft()-this.originalLeft); |
321 | } else { |
322 | this.originalLeft = this.currentLeft(); |
323 | this.originalTop = this.currentTop(); |
324 | } |
325 | |
326 | this.element.style.zIndex = this.originalZ; |
327 | |
328 | if(this.options.endeffect) |
329 | this.options.endeffect(this.element); |
330 | |
331 | Droppables.reset(); |
332 | }, |
333 | keyPress: function(event) { |
334 | if(this.active) { |
335 | if(event.keyCode==Event.KEY_ESC) { |
336 | this.finishDrag(event, false); |
337 | Event.stop(event); |
338 | } |
339 | } |
340 | }, |
341 | endDrag: function(event) { |
342 | if(this.active && this.dragging) { |
343 | this.finishDrag(event, true); |
344 | Event.stop(event); |
345 | } |
346 | this.active = false; |
347 | this.dragging = false; |
348 | }, |
349 | draw: function(event) { |
350 | var style = this.element.style; |
351 | this.originalX = this.element.offsetLeft - this.currentLeft() - this.originalLeft; |
352 | this.originalY = this.element.offsetTop - this.currentTop() - this.originalTop; |
353 | if((!this.options.constraint) || (this.options.constraint=='horizontal')) |
354 | style.left = ((event.clientX - this.originalX) - this.offsetX) + "px"; |
355 | if((!this.options.constraint) || (this.options.constraint=='vertical')) |
356 | style.top = ((event.clientY - this.originalY) - this.offsetY) + "px"; |
357 | if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering |
358 | }, |
359 | update: function(event) { |
360 | if(this.active) { |
361 | if(!this.dragging) { |
362 | var style = this.element.style; |
363 | this.dragging = true; |
364 | if(style.position=="") style.position = "relative"; |
365 | style.zIndex = this.options.zindex; |
366 | Draggables.notify('onStart', this); |
367 | if(this.options.starteffect) this.options.starteffect(this.element); |
368 | } |
369 | |
370 | Droppables.show(event, this.element); |
371 | this.draw(event); |
372 | if(this.options.change) this.options.change(this); |
373 | |
374 | // fix AppleWebKit rendering |
375 | if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); |
376 | |
377 | Event.stop(event); |
378 | } |
379 | } |
380 | } |
381 | |
382 | /*--------------------------------------------------------------------------*/ |
383 | |
384 | SortableObserver = Class.create(); |
385 | SortableObserver.prototype = { |
386 | initialize: function(element, observer) { |
387 | this.element = $(element); |
388 | this.observer = observer; |
389 | this.lastValue = Sortable.serialize(this.element); |
390 | }, |
391 | onStart: function() { |
392 | this.lastValue = Sortable.serialize(this.element); |
393 | }, |
394 | onEnd: function() { |
395 | if(this.lastValue != Sortable.serialize(this.element)) |
396 | this.observer(this.element) |
397 | } |
398 | } |
399 | |
400 | Sortable = { |
401 | sortables: new Array(), |
402 | options: function(element){ |
403 | var element = $(element); |
404 | for(var i=0;i<this.sortables.length;i++) |
405 | if(this.sortables[i].element == element) |
406 | return this.sortables[i]; |
407 | return null; |
408 | }, |
409 | destroy: function(element){ |
410 | var element = $(element); |
411 | for(var i=0;i<this.sortables.length;i++) { |
412 | if(this.sortables[i].element == element) { |
413 | var s = this.sortables[i]; |
414 | Draggables.removeObserver(s.element); |
415 | for(var j=0;j<s.droppables.length;j++) |
416 | Droppables.remove(s.droppables[j]); |
417 | for(var j=0;j<s.draggables.length;j++) |
418 | s.draggables[j].destroy(); |
419 | this.sortables.splice(i,1); |
420 | } |
421 | } |
422 | }, |
423 | create: function(element) { |
424 | var element = $(element); |
425 | var options = Object.extend({ |
426 | element: element, |
427 | tag: 'li', // assumes li children, override with tag: 'tagname' |
428 | overlap: 'vertical', // one of 'vertical', 'horizontal' |
429 | constraint: 'vertical', // one of 'vertical', 'horizontal', false |
430 | containment: element, // also takes array of elements (or id's); or false |
431 | handle: false, // or a CSS class |
432 | only: false, |
433 | hoverclass: null, |
434 | onChange: function() {}, |
435 | onUpdate: function() {} |
436 | }, arguments[1] || {}); |
437 | |
438 | // clear any old sortable with same element |
439 | this.destroy(element); |
440 | |
441 | // build options for the draggables |
442 | var options_for_draggable = { |
443 | revert: true, |
444 | constraint: options.constraint, |
445 | handle: handle }; |
446 | if(options.starteffect) |
447 | options_for_draggable.starteffect = options.starteffect; |
448 | if(options.reverteffect) |
449 | options_for_draggable.reverteffect = options.reverteffect; |
450 | if(options.endeffect) |
451 | options_for_draggable.endeffect = options.endeffect; |
452 | if(options.zindex) |
453 | options_for_draggable.zindex = options.zindex; |
454 | |
455 | // build options for the droppables |
456 | var options_for_droppable = { |
457 | overlap: options.overlap, |
458 | containment: options.containment, |
459 | hoverclass: options.hoverclass, |
460 | onHover: function(element, dropon, overlap) { |
461 | if(overlap>0.5) { |
462 | if(dropon.previousSibling != element) { |
463 | var oldParentNode = element.parentNode; |
464 | element.style.visibility = "hidden"; // fix gecko rendering |
465 | dropon.parentNode.insertBefore(element, dropon); |
466 | if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) |
467 | oldParentNode.sortable.onChange(element); |
468 | if(dropon.parentNode.sortable) |
469 | dropon.parentNode.sortable.onChange(element); |
470 | } |
471 | } else { |
472 | var nextElement = dropon.nextSibling || null; |
473 | if(nextElement != element) { |
474 | var oldParentNode = element.parentNode; |
475 | element.style.visibility = "hidden"; // fix gecko rendering |
476 | dropon.parentNode.insertBefore(element, nextElement); |
477 | if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) |
478 | oldParentNode.sortable.onChange(element); |
479 | if(dropon.parentNode.sortable) |
480 | dropon.parentNode.sortable.onChange(element); |
481 | } |
482 | } |
483 | } |
484 | } |
485 | |
486 | // fix for gecko engine |
487 | Element.cleanWhitespace(element); |
488 | |
489 | options.draggables = []; |
490 | options.droppables = []; |
491 | |
492 | // make it so |
493 | var elements = element.childNodes; |
494 | for (var i = 0; i < elements.length; i++) |
495 | if(elements[i].tagName && elements[i].tagName==options.tag.toUpperCase() && |
496 | (!options.only || (Element.Class.has(elements[i], options.only)))) { |
497 | |
498 | // handles are per-draggable |
499 | var handle = options.handle ? |
500 | Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i]; |
501 | |
502 | options.draggables.push(new Draggable(elements[i], Object.extend(options_for_draggable, { handle: handle }))); |
503 | |
504 | Droppables.add(elements[i], options_for_droppable); |
505 | options.droppables.push(elements[i]); |
506 | |
507 | } |
508 | |
509 | // keep reference |
510 | this.sortables.push(options); |
511 | |
512 | // for onupdate |
513 | Draggables.addObserver(new SortableObserver(element, options.onUpdate)); |
514 | |
515 | }, |
516 | serialize: function(element) { |
517 | var element = $(element); |
518 | var sortableOptions = this.options(element); |
519 | var options = Object.extend({ |
520 | tag: sortableOptions.tag, |
521 | only: sortableOptions.only, |
522 | name: element.id |
523 | }, arguments[1] || {}); |
524 | |
525 | var items = $(element).childNodes; |
526 | var queryComponents = new Array(); |
527 | |
528 | for(var i=0; i<items.length; i++) |
529 | if(items[i].tagName && items[i].tagName==options.tag.toUpperCase() && |
530 | (!options.only || (Element.Class.has(items[i], options.only)))) |
531 | queryComponents.push( |
532 | encodeURIComponent(options.name) + "[]=" + |
533 | encodeURIComponent(items[i].id.split("_")[1])); |
534 | |
535 | return queryComponents.join("&"); |
536 | } |
537 | } |