Passed
Pull Request — develop (#92)
by Felipe
06:21
created

Sortable.create   F

Complexity

Conditions 9
Paths 576

Size

Total Lines 108

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 576
nop 1
dl 0
loc 108
rs 3.3055
c 0
b 0
f 0

2 Functions

Rating   Name   Duplication   Size   Complexity  
A 0 10 3
A 0 4 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
//           (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, [email protected])
3
// 
4
// See scriptaculous.js for full license.
5
6
/*--------------------------------------------------------------------------*/
7
8
var Droppables = {
9
  drops: [],
10
11
  remove: function(element) {
12
    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
13
  },
14
15
  add: function(element) {
16
    element = $(element);
17
    var options = Object.extend({
18
      greedy:     true,
19
      hoverclass: null,
20
      tree:       false
21
    }, arguments[1] || {});
22
23
    // cache containers
24
    if(options.containment) {
25
      options._containers = [];
26
      var containment = options.containment;
27
      if((typeof containment == 'object') && 
28
        (containment.constructor == Array)) {
29
        containment.each( function(c) { options._containers.push($(c)) });
30
      } else {
31
        options._containers.push($(containment));
32
      }
33
    }
34
    
35
    if(options.accept) options.accept = [options.accept].flatten();
36
37
    Element.makePositioned(element); // fix IE
38
    options.element = element;
39
40
    this.drops.push(options);
41
  },
42
  
43
  findDeepestChild: function(drops) {
44
    deepest = drops[0];
0 ignored issues
show
Bug introduced by
The variable deepest seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.deepest.
Loading history...
45
      
46
    for (i = 1; i < drops.length; ++i)
0 ignored issues
show
Bug introduced by
The variable i seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.i.
Loading history...
47
      if (Element.isParent(drops[i].element, deepest.element))
48
        deepest = drops[i];
49
    
50
    return deepest;
51
  },
52
53
  isContained: function(element, drop) {
54
    var containmentNode;
55
    if(drop.tree) {
56
      containmentNode = element.treeNode; 
57
    } else {
58
      containmentNode = element.parentNode;
59
    }
60
    return drop._containers.detect(function(c) { return containmentNode == c });
61
  },
62
  
63
  isAffected: function(point, element, drop) {
64
    return (
65
      (drop.element!=element) &&
66
      ((!drop._containers) ||
67
        this.isContained(element, drop)) &&
68
      ((!drop.accept) ||
69
        (Element.classNames(element).detect( 
70
          function(v) { return drop.accept.include(v) } ) )) &&
71
      Position.within(drop.element, point[0], point[1]) );
72
  },
73
74
  deactivate: function(drop) {
75
    if(drop.hoverclass)
76
      Element.removeClassName(drop.element, drop.hoverclass);
77
    this.last_active = null;
78
  },
79
80
  activate: function(drop) {
81
    if(drop.hoverclass)
82
      Element.addClassName(drop.element, drop.hoverclass);
83
    this.last_active = drop;
84
  },
85
86
  show: function(point, element) {
87
    if(!this.drops.length) return;
88
    var affected = [];
89
    
90
    if(this.last_active) this.deactivate(this.last_active);
91
    this.drops.each( function(drop) {
92
      if(Droppables.isAffected(point, element, drop))
93
        affected.push(drop);
94
    });
95
        
96
    if(affected.length>0) {
97
      drop = Droppables.findDeepestChild(affected);
0 ignored issues
show
Bug introduced by
The variable drop seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.drop.
Loading history...
98
      Position.within(drop.element, point[0], point[1]);
99
      if(drop.onHover)
100
        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
101
      
102
      Droppables.activate(drop);
103
    }
104
  },
105
106
  fire: function(event, element) {
107
    if(!this.last_active) return;
108
    Position.prepare();
109
110
    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
111
      if (this.last_active.onDrop) 
112
        this.last_active.onDrop(element, this.last_active.element, event);
113
  },
114
115
  reset: function() {
116
    if(this.last_active)
117
      this.deactivate(this.last_active);
118
  }
119
}
120
121
var Draggables = {
122
  drags: [],
123
  observers: [],
124
  
125
  register: function(draggable) {
126
    if(this.drags.length == 0) {
127
      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
128
      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
129
      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
130
      
131
      Event.observe(document, "mouseup", this.eventMouseUp);
132
      Event.observe(document, "mousemove", this.eventMouseMove);
133
      Event.observe(document, "keypress", this.eventKeypress);
134
    }
135
    this.drags.push(draggable);
136
  },
137
  
138
  unregister: function(draggable) {
139
    this.drags = this.drags.reject(function(d) { return d==draggable });
140
    if(this.drags.length == 0) {
141
      Event.stopObserving(document, "mouseup", this.eventMouseUp);
142
      Event.stopObserving(document, "mousemove", this.eventMouseMove);
143
      Event.stopObserving(document, "keypress", this.eventKeypress);
144
    }
145
  },
146
  
147
  activate: function(draggable) {
148
    window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
149
    this.activeDraggable = draggable;
150
  },
151
  
152
  deactivate: function() {
153
    this.activeDraggable = null;
154
  },
155
  
156
  updateDrag: function(event) {
157
    if(!this.activeDraggable) return;
158
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
159
    // Mozilla-based browsers fire successive mousemove events with
160
    // the same coordinates, prevent needless redrawing (moz bug?)
161
    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
162
    this._lastPointer = pointer;
163
    this.activeDraggable.updateDrag(event, pointer);
164
  },
165
  
166
  endDrag: function(event) {
167
    if(!this.activeDraggable) return;
168
    this._lastPointer = null;
169
    this.activeDraggable.endDrag(event);
170
    this.activeDraggable = null;
171
  },
172
  
173
  keyPress: function(event) {
174
    if(this.activeDraggable)
175
      this.activeDraggable.keyPress(event);
176
  },
177
  
178
  addObserver: function(observer) {
179
    this.observers.push(observer);
180
    this._cacheObserverCallbacks();
181
  },
182
  
183
  removeObserver: function(element) {  // element instead of observer fixes mem leaks
184
    this.observers = this.observers.reject( function(o) { return o.element==element });
185
    this._cacheObserverCallbacks();
186
  },
187
  
188
  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
189
    if(this[eventName+'Count'] > 0)
190
      this.observers.each( function(o) {
191
        if(o[eventName]) o[eventName](eventName, draggable, event);
192
      });
193
  },
194
  
195
  _cacheObserverCallbacks: function() {
196
    ['onStart','onEnd','onDrag'].each( function(eventName) {
197
      Draggables[eventName+'Count'] = Draggables.observers.select(
198
        function(o) { return o[eventName]; }
199
      ).length;
200
    });
201
  }
202
}
203
204
/*--------------------------------------------------------------------------*/
205
206
var Draggable = Class.create();
207
Draggable.prototype = {
208
  initialize: function(element) {
209
    var options = Object.extend({
210
      handle: false,
211
      starteffect: function(element) {
212
        element._opacity = Element.getOpacity(element); 
213
        new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
0 ignored issues
show
Unused Code Best Practice introduced by
The object created with new Effect.Opacity(eleme...umberLiteralNode(0.7)}) is not used but discarded. Consider invoking another function instead of a constructor if you are doing this purely for side effects.
Loading history...
214
      },
215
      reverteffect: function(element, top_offset, left_offset) {
216
        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
217
        element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur});
218
      },
219
      endeffect: function(element) {
220
        var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0
221
        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity}); 
0 ignored issues
show
Unused Code Best Practice introduced by
The object created with new Effect.Opacity(eleme...Node(toOpacity,false)}) is not used but discarded. Consider invoking another function instead of a constructor if you are doing this purely for side effects.
Loading history...
222
      },
223
      zindex: 1000,
224
      revert: false,
225
      scroll: false,
226
      scrollSensitivity: 20,
227
      scrollSpeed: 15,
228
      snap: false   // false, or xy or [x,y] or function(x,y){ return [x,y] }
229
    }, arguments[1] || {});
230
231
    this.element = $(element);
232
    
233
    if(options.handle && (typeof options.handle == 'string')) {
234
      var h = Element.childrenWithClassName(this.element, options.handle, true);
235
      if(h.length>0) this.handle = h[0];
236
    }
237
    if(!this.handle) this.handle = $(options.handle);
238
    if(!this.handle) this.handle = this.element;
239
    
240
    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML)
241
      options.scroll = $(options.scroll);
242
243
    Element.makePositioned(this.element); // fix IE    
244
245
    this.delta    = this.currentDelta();
246
    this.options  = options;
247
    this.dragging = false;   
248
249
    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
250
    Event.observe(this.handle, "mousedown", this.eventMouseDown);
251
    
252
    Draggables.register(this);
253
  },
254
  
255
  destroy: function() {
256
    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
257
    Draggables.unregister(this);
258
  },
259
  
260
  currentDelta: function() {
261
    return([
262
      parseInt(Element.getStyle(this.element,'left') || '0'),
263
      parseInt(Element.getStyle(this.element,'top') || '0')]);
264
  },
265
  
266
  initDrag: function(event) {
267
    if(Event.isLeftClick(event)) {    
268
      // abort on form elements, fixes a Firefox issue
269
      var src = Event.element(event);
270
      if(src.tagName && (
271
        src.tagName=='INPUT' ||
272
        src.tagName=='SELECT' ||
273
        src.tagName=='OPTION' ||
274
        src.tagName=='BUTTON' ||
275
        src.tagName=='TEXTAREA')) return;
276
        
277
      if(this.element._revert) {
278
        this.element._revert.cancel();
279
        this.element._revert = null;
280
      }
281
      
282
      var pointer = [Event.pointerX(event), Event.pointerY(event)];
283
      var pos     = Position.cumulativeOffset(this.element);
284
      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
285
      
286
      Draggables.activate(this);
287
      Event.stop(event);
288
    }
289
  },
290
  
291
  startDrag: function(event) {
292
    this.dragging = true;
293
    
294
    if(this.options.zindex) {
295
      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
296
      this.element.style.zIndex = this.options.zindex;
297
    }
298
    
299
    if(this.options.ghosting) {
300
      this._clone = this.element.cloneNode(true);
301
      Position.absolutize(this.element);
302
      this.element.parentNode.insertBefore(this._clone, this.element);
303
    }
304
    
305
    if(this.options.scroll) {
306
      if (this.options.scroll == window) {
307
        var where = this._getWindowScroll(this.options.scroll);
308
        this.originalScrollLeft = where.left;
309
        this.originalScrollTop = where.top;
310
      } else {
311
        this.originalScrollLeft = this.options.scroll.scrollLeft;
312
        this.originalScrollTop = this.options.scroll.scrollTop;
313
      }
314
    }
315
    
316
    Draggables.notify('onStart', this, event);
317
    if(this.options.starteffect) this.options.starteffect(this.element);
318
  },
319
  
320
  updateDrag: function(event, pointer) {
321
    if(!this.dragging) this.startDrag(event);
322
    Position.prepare();
323
    Droppables.show(pointer, this.element);
324
    Draggables.notify('onDrag', this, event);
325
    this.draw(pointer);
326
    if(this.options.change) this.options.change(this);
327
    
328
    if(this.options.scroll) {
329
      this.stopScrolling();
330
      
331
      var p;
332
      if (this.options.scroll == window) {
333
        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
0 ignored issues
show
introduced by
This code is unreachable and can thus be removed without consequences.
Loading history...
Comprehensibility Compatibility Best Practice introduced by
The use of the with statement is discouraged as it makes your code harder to understand and may not be forward compatible with newer versions of javascript.
Loading history...
334
      } else {
335
        p = Position.page(this.options.scroll);
336
        p[0] += this.options.scroll.scrollLeft;
337
        p[1] += this.options.scroll.scrollTop;
338
        p.push(p[0]+this.options.scroll.offsetWidth);
339
        p.push(p[1]+this.options.scroll.offsetHeight);
340
      }
341
      var speed = [0,0];
342
      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
343
      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
344
      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
345
      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
346
      this.startScrolling(speed);
347
    }
348
    
349
    // fix AppleWebKit rendering
350
    if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
351
    
352
    Event.stop(event);
353
  },
354
  
355
  finishDrag: function(event, success) {
356
    this.dragging = false;
357
358
    if(this.options.ghosting) {
359
      Position.relativize(this.element);
360
      Element.remove(this._clone);
361
      this._clone = null;
362
    }
363
364
    if(success) Droppables.fire(event, this.element);
365
    Draggables.notify('onEnd', this, event);
366
367
    var revert = this.options.revert;
368
    if(revert && typeof revert == 'function') revert = revert(this.element);
369
    
370
    var d = this.currentDelta();
371
    if(revert && this.options.reverteffect) {
372
      this.options.reverteffect(this.element, 
373
        d[1]-this.delta[1], d[0]-this.delta[0]);
374
    } else {
375
      this.delta = d;
376
    }
377
378
    if(this.options.zindex)
379
      this.element.style.zIndex = this.originalZ;
380
381
    if(this.options.endeffect) 
382
      this.options.endeffect(this.element);
383
384
    Draggables.deactivate(this);
385
    Droppables.reset();
386
  },
387
  
388
  keyPress: function(event) {
389
    if(event.keyCode!=Event.KEY_ESC) return;
390
    this.finishDrag(event, false);
391
    Event.stop(event);
392
  },
393
  
394
  endDrag: function(event) {
395
    if(!this.dragging) return;
396
    this.stopScrolling();
397
    this.finishDrag(event, true);
398
    Event.stop(event);
399
  },
400
  
401
  draw: function(point) {
402
    var pos = Position.cumulativeOffset(this.element);
403
    var d = this.currentDelta();
404
    pos[0] -= d[0]; pos[1] -= d[1];
405
    
406
    if(this.options.scroll && (this.options.scroll != window)) {
407
      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
408
      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
409
    }
410
    
411
    var p = [0,1].map(function(i){ 
412
      return (point[i]-pos[i]-this.offset[i]) 
413
    }.bind(this));
414
    
415
    if(this.options.snap) {
416
      if(typeof this.options.snap == 'function') {
417
        p = this.options.snap(p[0],p[1],this);
418
      } else {
419
      if(this.options.snap instanceof Array) {
420
        p = p.map( function(v, i) {
421
          return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
422
      } else {
423
        p = p.map( function(v) {
424
          return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
425
      }
426
    }}
427
    
428
    var style = this.element.style;
429
    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
430
      style.left = p[0] + "px";
431
    if((!this.options.constraint) || (this.options.constraint=='vertical'))
432
      style.top  = p[1] + "px";
433
    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
434
  },
435
  
436
  stopScrolling: function() {
437
    if(this.scrollInterval) {
438
      clearInterval(this.scrollInterval);
439
      this.scrollInterval = null;
440
      Draggables._lastScrollPointer = null;
441
    }
442
  },
443
  
444
  startScrolling: function(speed) {
445
    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
446
    this.lastScrolled = new Date();
447
    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
448
  },
449
  
450
  scroll: function() {
451
    var current = new Date();
452
    var delta = current - this.lastScrolled;
453
    this.lastScrolled = current;
454
    if(this.options.scroll == window) {
455
      with (this._getWindowScroll(this.options.scroll)) {
0 ignored issues
show
Comprehensibility Compatibility Best Practice introduced by
The use of the with statement is discouraged as it makes your code harder to understand and may not be forward compatible with newer versions of javascript.
Loading history...
introduced by
This code is unreachable and can thus be removed without consequences.
Loading history...
456
        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
457
          var d = delta / 1000;
458
          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
459
        }
460
      }
461
    } else {
462
      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
463
      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
464
    }
465
    
466
    Position.prepare();
467
    Droppables.show(Draggables._lastPointer, this.element);
468
    Draggables.notify('onDrag', this);
469
    Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
470
    Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
471
    Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
472
    if (Draggables._lastScrollPointer[0] < 0)
473
      Draggables._lastScrollPointer[0] = 0;
474
    if (Draggables._lastScrollPointer[1] < 0)
475
      Draggables._lastScrollPointer[1] = 0;
476
    this.draw(Draggables._lastScrollPointer);
477
    
478
    if(this.options.change) this.options.change(this);
479
  },
480
  
481
  _getWindowScroll: function(w) {
482
    var T, L, W, H;
483
    with (w.document) {
0 ignored issues
show
introduced by
This code is unreachable and can thus be removed without consequences.
Loading history...
Comprehensibility Compatibility Best Practice introduced by
The use of the with statement is discouraged as it makes your code harder to understand and may not be forward compatible with newer versions of javascript.
Loading history...
484
      if (w.document.documentElement && documentElement.scrollTop) {
485
        T = documentElement.scrollTop;
486
        L = documentElement.scrollLeft;
487
      } else if (w.document.body) {
488
        T = body.scrollTop;
489
        L = body.scrollLeft;
490
      }
491
      if (w.innerWidth) {
492
        W = w.innerWidth;
493
        H = w.innerHeight;
494
      } else if (w.document.documentElement && documentElement.clientWidth) {
495
        W = documentElement.clientWidth;
496
        H = documentElement.clientHeight;
497
      } else {
498
        W = body.offsetWidth;
499
        H = body.offsetHeight
500
      }
501
    }
502
    return { top: T, left: L, width: W, height: H };
0 ignored issues
show
Bug introduced by
The variable T does not seem to be initialized in case w.document.body on line 487 is false. Are you sure this can never be the case?
Loading history...
Bug introduced by
The variable L does not seem to be initialized in case w.document.body on line 487 is false. Are you sure this can never be the case?
Loading history...
503
  }
504
}
505
506
/*--------------------------------------------------------------------------*/
507
508
var SortableObserver = Class.create();
509
SortableObserver.prototype = {
510
  initialize: function(element, observer) {
511
    this.element   = $(element);
512
    this.observer  = observer;
513
    this.lastValue = Sortable.serialize(this.element);
514
  },
515
  
516
  onStart: function() {
517
    this.lastValue = Sortable.serialize(this.element);
518
  },
519
  
520
  onEnd: function() {
521
    Sortable.unmark();
522
    if(this.lastValue != Sortable.serialize(this.element))
523
      this.observer(this.element)
524
  }
525
}
526
527
var Sortable = {
528
  sortables: {},
529
  
530
  _findRootElement: function(element) {
531
    while (element.tagName != "BODY") {  
532
      if(element.id && Sortable.sortables[element.id]) return element;
533
      element = element.parentNode;
534
    }
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
535
  },
536
537
  options: function(element) {
538
    element = Sortable._findRootElement($(element));
539
    if(!element) return;
540
    return Sortable.sortables[element.id];
541
  },
542
  
543
  destroy: function(element){
544
    var s = Sortable.options(element);
545
    
546
    if(s) {
547
      Draggables.removeObserver(s.element);
548
      s.droppables.each(function(d){ Droppables.remove(d) });
549
      s.draggables.invoke('destroy');
550
      
551
      delete Sortable.sortables[s.element.id];
552
    }
553
  },
554
555
  create: function(element) {
556
    element = $(element);
557
    var options = Object.extend({ 
558
      element:     element,
559
      tag:         'li',       // assumes li children, override with tag: 'tagname'
560
      dropOnEmpty: false,
561
      tree:        false,
562
      treeTag:     'ul',
563
      overlap:     'vertical', // one of 'vertical', 'horizontal'
564
      constraint:  'vertical', // one of 'vertical', 'horizontal', false
565
      containment: element,    // also takes array of elements (or id's); or false
566
      handle:      false,      // or a CSS class
567
      only:        false,
568
      hoverclass:  null,
569
      ghosting:    false,
570
      scroll:      false,
571
      scrollSensitivity: 20,
572
      scrollSpeed: 15,
573
      format:      /^[^_]*_(.*)$/,
574
      onChange:    Prototype.emptyFunction,
575
      onUpdate:    Prototype.emptyFunction
576
    }, arguments[1] || {});
577
578
    // clear any old sortable with same element
579
    this.destroy(element);
580
581
    // build options for the draggables
582
    var options_for_draggable = {
583
      revert:      true,
584
      scroll:      options.scroll,
585
      scrollSpeed: options.scrollSpeed,
586
      scrollSensitivity: options.scrollSensitivity,
587
      ghosting:    options.ghosting,
588
      constraint:  options.constraint,
589
      handle:      options.handle };
590
591
    if(options.starteffect)
592
      options_for_draggable.starteffect = options.starteffect;
593
594
    if(options.reverteffect)
595
      options_for_draggable.reverteffect = options.reverteffect;
596
    else
597
      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
598
        element.style.top  = 0;
599
        element.style.left = 0;
600
      };
601
602
    if(options.endeffect)
603
      options_for_draggable.endeffect = options.endeffect;
604
605
    if(options.zindex)
606
      options_for_draggable.zindex = options.zindex;
607
608
    // build options for the droppables  
609
    var options_for_droppable = {
610
      overlap:     options.overlap,
611
      containment: options.containment,
612
      tree:        options.tree,
613
      hoverclass:  options.hoverclass,
614
      onHover:     Sortable.onHover
615
      //greedy:      !options.dropOnEmpty
616
    }
617
    
618
    var options_for_tree = {
619
      onHover:      Sortable.onEmptyHover,
620
      overlap:      options.overlap,
621
      containment:  options.containment,
622
      hoverclass:   options.hoverclass
623
    }
624
625
    // fix for gecko engine
626
    Element.cleanWhitespace(element); 
627
628
    options.draggables = [];
629
    options.droppables = [];
630
631
    // drop on empty handling
632
    if(options.dropOnEmpty || options.tree) {
633
      Droppables.add(element, options_for_tree);
634
      options.droppables.push(element);
635
    }
636
637
    (this.findElements(element, options) || []).each( function(e) {
638
      // handles are per-draggable
639
      var handle = options.handle ? 
640
        Element.childrenWithClassName(e, options.handle)[0] : e;    
641
      options.draggables.push(
642
        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
643
      Droppables.add(e, options_for_droppable);
644
      if(options.tree) e.treeNode = element;
645
      options.droppables.push(e);      
646
    });
647
    
648
    if(options.tree) {
649
      (Sortable.findTreeElements(element, options) || []).each( function(e) {
650
        Droppables.add(e, options_for_tree);
651
        e.treeNode = element;
652
        options.droppables.push(e);
653
      });
654
    }
655
656
    // keep reference
657
    this.sortables[element.id] = options;
658
659
    // for onupdate
660
    Draggables.addObserver(new SortableObserver(element, options.onUpdate));
661
662
  },
663
664
  // return all suitable-for-sortable elements in a guaranteed order
665
  findElements: function(element, options) {
666
    return Element.findChildren(
667
      element, options.only, options.tree ? true : false, options.tag);
668
  },
669
  
670
  findTreeElements: function(element, options) {
671
    return Element.findChildren(
672
      element, options.only, options.tree ? true : false, options.treeTag);
673
  },
674
675
  onHover: function(element, dropon, overlap) {
676
    if(Element.isParent(dropon, element)) return;
677
678
    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
679
      return;
0 ignored issues
show
Unused Code introduced by
This return has no effect and can be removed.
Loading history...
680
    } else if(overlap>0.5) {
681
      Sortable.mark(dropon, 'before');
682
      if(dropon.previousSibling != element) {
683
        var oldParentNode = element.parentNode;
684
        element.style.visibility = "hidden"; // fix gecko rendering
685
        dropon.parentNode.insertBefore(element, dropon);
686
        if(dropon.parentNode!=oldParentNode) 
687
          Sortable.options(oldParentNode).onChange(element);
688
        Sortable.options(dropon.parentNode).onChange(element);
689
      }
690
    } else {
691
      Sortable.mark(dropon, 'after');
692
      var nextElement = dropon.nextSibling || null;
693
      if(nextElement != element) {
694
        var oldParentNode = element.parentNode;
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable oldParentNode already seems to be declared on line 683. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
695
        element.style.visibility = "hidden"; // fix gecko rendering
696
        dropon.parentNode.insertBefore(element, nextElement);
697
        if(dropon.parentNode!=oldParentNode) 
698
          Sortable.options(oldParentNode).onChange(element);
699
        Sortable.options(dropon.parentNode).onChange(element);
700
      }
701
    }
702
  },
703
  
704
  onEmptyHover: function(element, dropon, overlap) {
705
    var oldParentNode = element.parentNode;
706
    var droponOptions = Sortable.options(dropon);
707
        
708
    if(!Element.isParent(dropon, element)) {
709
      var index;
710
      
711
      var children = Sortable.findElements(dropon, {tag: droponOptions.tag});
712
      var child = null;
713
            
714
      if(children) {
715
        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
716
        
717
        for (index = 0; index < children.length; index += 1) {
718
          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
719
            offset -= Element.offsetSize (children[index], droponOptions.overlap);
720
          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
721
            child = index + 1 < children.length ? children[index + 1] : null;
722
            break;
723
          } else {
724
            child = children[index];
725
            break;
726
          }
727
        }
728
      }
729
      
730
      dropon.insertBefore(element, child);
731
      
732
      Sortable.options(oldParentNode).onChange(element);
733
      droponOptions.onChange(element);
734
    }
735
  },
736
737
  unmark: function() {
738
    if(Sortable._marker) Element.hide(Sortable._marker);
739
  },
740
741
  mark: function(dropon, position) {
742
    // mark on ghosting only
743
    var sortable = Sortable.options(dropon.parentNode);
744
    if(sortable && !sortable.ghosting) return; 
745
746
    if(!Sortable._marker) {
747
      Sortable._marker = $('dropmarker') || document.createElement('DIV');
748
      Element.hide(Sortable._marker);
749
      Element.addClassName(Sortable._marker, 'dropmarker');
750
      Sortable._marker.style.position = 'absolute';
751
      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
752
    }    
753
    var offsets = Position.cumulativeOffset(dropon);
754
    Sortable._marker.style.left = offsets[0] + 'px';
755
    Sortable._marker.style.top = offsets[1] + 'px';
756
    
757
    if(position=='after')
758
      if(sortable.overlap == 'horizontal') 
759
        Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
760
      else
761
        Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
762
    
763
    Element.show(Sortable._marker);
764
  },
765
  
766
  _tree: function(element, options, parent) {
767
    var children = Sortable.findElements(element, options) || [];
768
  
769
    for (var i = 0; i < children.length; ++i) {
770
      var match = children[i].id.match(options.format);
771
772
      if (!match) continue;
773
      
774
      var child = {
775
        id: encodeURIComponent(match ? match[1] : null),
776
        element: element,
777
        parent: parent,
778
        children: new Array,
0 ignored issues
show
Coding Style Best Practice introduced by
Using the Array constructor is generally discouraged. Consider using an array literal instead.
Loading history...
779
        position: parent.children.length,
780
        container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase())
781
      }
782
      
783
      /* Get the element containing the children and recurse over it */
784
      if (child.container)
785
        this._tree(child.container, options, child)
786
      
787
      parent.children.push (child);
788
    }
789
790
    return parent; 
791
  },
792
793
  /* Finds the first element of the given tag type within a parent element.
794
    Used for finding the first LI[ST] within a L[IST]I[TEM].*/
795
  _findChildrenElement: function (element, containerTag) {
796
    if (element && element.hasChildNodes)
797
      for (var i = 0; i < element.childNodes.length; ++i)
798
        if (element.childNodes[i].tagName == containerTag)
799
          return element.childNodes[i];
800
  
801
    return null;
802
  },
803
804
  tree: function(element) {
805
    element = $(element);
806
    var sortableOptions = this.options(element);
807
    var options = Object.extend({
808
      tag: sortableOptions.tag,
809
      treeTag: sortableOptions.treeTag,
810
      only: sortableOptions.only,
811
      name: element.id,
812
      format: sortableOptions.format
813
    }, arguments[1] || {});
814
    
815
    var root = {
816
      id: null,
817
      parent: null,
818
      children: new Array,
0 ignored issues
show
Coding Style Best Practice introduced by
Using the Array constructor is generally discouraged. Consider using an array literal instead.
Loading history...
819
      container: element,
820
      position: 0
821
    }
822
    
823
    return Sortable._tree (element, options, root);
824
  },
825
826
  /* Construct a [i] index for a particular node */
827
  _constructIndex: function(node) {
828
    var index = '';
829
    do {
830
      if (node.id) index = '[' + node.position + ']' + index;
831
    } while ((node = node.parent) != null);
832
    return index;
833
  },
834
835
  sequence: function(element) {
836
    element = $(element);
837
    var options = Object.extend(this.options(element), arguments[1] || {});
838
    
839
    return $(this.findElements(element, options) || []).map( function(item) {
840
      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
841
    });
842
  },
843
844
  setSequence: function(element, new_sequence) {
845
    element = $(element);
846
    var options = Object.extend(this.options(element), arguments[2] || {});
847
    
848
    var nodeMap = {};
849
    this.findElements(element, options).each( function(n) {
850
        if (n.id.match(options.format))
851
            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
852
        n.parentNode.removeChild(n);
853
    });
854
   
855
    new_sequence.each(function(ident) {
856
      var n = nodeMap[ident];
857
      if (n) {
858
        n[1].appendChild(n[0]);
859
        delete nodeMap[ident];
860
      }
861
    });
862
  },
863
  
864
  serialize: function(element) {
865
    element = $(element);
866
    var options = Object.extend(Sortable.options(element), arguments[1] || {});
867
    var name = encodeURIComponent(
868
      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
869
    
870
    if (options.tree) {
871
      return Sortable.tree(element, arguments[1]).children.map( function (item) {
872
        return [name + Sortable._constructIndex(item) + "=" + 
873
                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
874
      }).flatten().join('&');
875
    } else {
876
      return Sortable.sequence(element, arguments[1]).map( function(item) {
877
        return name + "[]=" + encodeURIComponent(item);
878
      }).join('&');
879
    }
880
  }
881
}
882
883
/* Returns true if child is contained within element */
884
Element.isParent = function(child, element) {
885
  if (!child.parentNode || child == element) return false;
886
887
  if (child.parentNode == element) return true;
888
889
  return Element.isParent(child.parentNode, element);
890
}
891
892
Element.findChildren = function(element, only, recursive, tagName) {    
893
  if(!element.hasChildNodes()) return null;
894
  tagName = tagName.toUpperCase();
895
  if(only) only = [only].flatten();
896
  var elements = [];
897
  $A(element.childNodes).each( function(e) {
898
    if(e.tagName && e.tagName.toUpperCase()==tagName &&
899
      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
900
        elements.push(e);
901
    if(recursive) {
902
      var grandchildren = Element.findChildren(e, only, recursive, tagName);
903
      if(grandchildren) elements.push(grandchildren);
904
    }
905
  });
906
907
  return (elements.length>0 ? elements.flatten() : []);
908
}
909
910
Element.offsetSize = function (element, type) {
911
  if (type == 'vertical' || type == 'height')
912
    return element.offsetHeight;
913
  else
914
    return element.offsetWidth;
915
}