public
Description: scriptaculous sortable w/ multiple selectable elements
Homepage: http://simplificator.com/scriptaculous-multidrag/
Clone URL: git://github.com/panter/scriptaculous-multidrag.git
scriptaculous-multidrag / multidrag.js
100644 152 lines (134 sloc) 5.197 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// script.aculo.us multi drag and drop add on
//
// See http://www.github.com/panter/scriptaculous-multidrag/
// and http://script.aculo.us/
// for more information
//
// script.aculo.us is freely distributable under the terms of an MIT-style
// license. For details, see the web site:http://script.aculo.us/
//
// The multi d&d add on code is in the public domain.
//
// This add-on enables multiple selections for scripty draggables and
// sortables.
//
// Please note that the sortable add on needs a slightly patched version of
// dragdrop.js.
 
//////////// utility methods ////////////////////////////////////////////////
 
// returns an array of elements that are in state activated.
function getActivatedElementIds() {
  activated = [].concat($$('.activated'));
  activated = activated.pluck('id').uniq();
  return activated;
}
 
// add needed listeners for multidrag on the given element
function prepareMultidrag(element) {
  element.observe('mousedown', function(e) {
    if (e.shiftKey &&
        !element.hasClassName('activated') &&
        $$('.activated').size() > 0) {
      activateSiblings(element.previousSiblings()) ||
        activateSiblings(element.nextSiblings());
    }
    element.toggleClassName('activated');
  });
}
 
// activates the elements of the given siblings array till the first activated
// element is found (needed for shift click)
function activateSiblings(siblings) {
  if (!siblings.find(function(s) { return s.hasClassName('activated') })) {
    return false;
  }
  siblings.each(function(s) {
      if (s.hasClassName('activated')) { throw $break; }
      s.addClassName('activated');
    });
  return true;
}
 
function deactivateAll() {
  $$('.activated').each(function(e) { e.removeClassName('activated'); });
}
 
function activateAll() {
  Draggables.drags.each(function(d) { d.element.addClassName('activated'); });
}
 
// the effect to apply when drag revert happens: this places to element
// immediatly on its targeted position
function immediateRevertEffect(element, top_offset, left_offset) {
  postop = parseFloat(element.style.top) - top_offset;
  posleft = parseFloat(element.style.left) - left_offset;
  element.setStyle({
    left: posleft.round() + 'px',
    top: postop.round() + 'px'
  });
}
 
//////////// observers //////////////////////////////////////////////////////
 
// scripty draggable observer that paints a box on the current draggable,
// indicating how many objects are activated currently
var MultidragObserver = Class.create({
  initialize: function(container) {
    // reset all droppables on container click
    Event.observe(container, 'click', function(e) {
        if (container.id == e.element().id) {
          deactivateAll();
        }
      });
    // add mouse down listener to activate draggables
    Draggables.drags.each(function(draggable) {
      prepareMultidrag(draggable.element);
    });
  },
 
  // called on drag start: displays a info box on the draggable showing how many
  // elements are activated for this drag if this is a multi element drag
  onStart: function(eventName, draggable, domEvent) {
    draggable.element.addClassName('activated');
    draggable._clone.addClassName('activated');
    activated = getActivatedElementIds();
    if (activated.length > 1) {
      info = new Element('div', { 'class': 'dragcount' });
      info.insert(activated.length);
      draggable.element.insertBefore(info, draggable.element.firstChild);
    }
  },
 
  // called on drag end: removes drag count info boxes and reorders if the drag
  // target was the sortable.
  onEnd: function(eventName, draggable, domEvent) {
    $$('.dragcount').each(function(e) { e.remove() });
  }
 
});
 
 
// scrypty draggable observer that supports reordering more than one element
var MultisortObserver = Class.create({
  initialize: function(container) {
    this.container = container;
  },
 
  // called on drag start: remembers element order
  onStart: function(eventName, draggable, domEvent) {
    this.lastSequence = Sortable.sequence(this.container);
  },
 
  // called on drag end: reorders the sortable elements when multiple elements
  // were dropped.
  onEnd: function(eventName, draggable, domEvent) {
    // do nothing if the drop area has reveived the elements
    if (null == Sortable._marker) { return; }
    draggableId = draggable.element.id.substring(1+draggable.element.id.lastIndexOf('_'));
    origindex = this.lastSequence.indexOf(draggableId);
    // do nothing if the draggable is still on its place
    newSequence = Sortable.sequence(this.container);
    if (origindex == newSequence.indexOf(draggableId)) {
        return;
    }
    // drop the other activated elements near the just dropped draggable
    rightSibling = draggable.element;
    parentNode = draggable.element.parentNode;
    $$('.activated').each(function(e) {
      if (draggable.element.id != e.id) {
        id = e.id.substring(1+e.id.indexOf('_'));
        if (this.lastSequence.indexOf(id) < origindex) {
          parentNode.insertBefore(e, draggable.element);
        } else {
          parentNode.insertBefore(e, rightSibling.nextSibling);
          rightSibling = e;
        }
      }
    }, this);
  }
 
});