activeadmin_sortable_table 1.1.2 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +1 -1
- data/app/assets/javascripts/activeadmin_sortable_table.js +16 -2
- data/lib/active_admin/sortable_table/version.rb +1 -1
- data/spec/dummy/app/assets/javascripts/active_admin.js.coffee +1 -1
- data/spec/dummy/app/assets/javascripts/jquery.simulate.drag-sortable.js +235 -0
- data/spec/features/drag_and_drop_spec.rb +96 -40
- data/spec/features/move_to_top_spec.rb +17 -9
- metadata +5 -5
- data/spec/dummy/app/assets/javascripts/jquery.simulate.js +0 -314
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 66207fb91f6c2abf4f7a29107fabb9ace050c52f
|
4
|
+
data.tar.gz: 679a10bc3b27667d32fe43a9a86dd14855bed9d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5ce91b8a8994c8322f90e941a4c5ba01cf7bcd0de42e5080eac43f8067969f0ab18871f945311f5b3a87ed276dd07e73b8045ec0f0a5c9e08754ef08ba86f48
|
7
|
+
data.tar.gz: 3686d18158dc53c1adaf5a581e72cced145057f33a8239e22448ed092b020e045b4302b3106eddecfbf9675eb78785b7bfd3324e3ddf91d5f333b3c24479193d
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -9,16 +9,30 @@
|
|
9
9
|
update: function(event, ui) {
|
10
10
|
var item = ui.item.find('[data-sort-url]');
|
11
11
|
var url = item.data('sort-url');
|
12
|
-
var actionOnSuccess = item.data('sort-success-action');
|
13
12
|
var customParams = {};
|
14
13
|
if (typeof item.data('sort-custom-params') === 'object') {
|
15
14
|
customParams = item.data('sort-custom-params');
|
16
15
|
}
|
17
16
|
|
17
|
+
var nextElement = ui.item[0].nextElementSibling;
|
18
|
+
|
19
|
+
var nextPosition = $(nextElement).find('[data-position]').data('position');
|
20
|
+
var currentPosition = ui.item.find('[data-position]').data('position');
|
21
|
+
|
22
|
+
if(nextPosition === undefined || nextPosition > currentPosition) {
|
23
|
+
// moved down
|
24
|
+
var previousElement = ui.item[0].previousElementSibling;
|
25
|
+
var previousPosition = $(previousElement).find('[data-position]').data('position');
|
26
|
+
var newPosition = previousPosition;
|
27
|
+
} else {
|
28
|
+
// moved up
|
29
|
+
var newPosition = nextPosition;
|
30
|
+
}
|
31
|
+
|
18
32
|
$.ajax({
|
19
33
|
url: url,
|
20
34
|
type: 'post',
|
21
|
-
data: $.extend(customParams, { position:
|
35
|
+
data: $.extend(customParams, { position: newPosition }),
|
22
36
|
error: function () { console.error('Saving sortable error'); },
|
23
37
|
success: function () {
|
24
38
|
location.href = location.href;
|
@@ -0,0 +1,235 @@
|
|
1
|
+
(function($) {
|
2
|
+
/*
|
3
|
+
* Simulate drag of a JQuery UI sortable list
|
4
|
+
* Repository: https://github.com/mattheworiordan/jquery.simulate.drag-sortable.js
|
5
|
+
* Author: http://mattheworiordan.com
|
6
|
+
*
|
7
|
+
* options are:
|
8
|
+
* - move: move item up (positive) or down (negative) by Integer amount
|
9
|
+
* - dropOn: move item to a new linked list, move option now represents position in the new list (zero indexed)
|
10
|
+
* - handle: selector for the draggable handle element (optional)
|
11
|
+
* - listItem: selector to limit which sibling items can be used for reordering
|
12
|
+
* - placeHolder: if a placeholder is used during dragging, we need to consider it's height
|
13
|
+
* - tolerance: (optional) number of pixels to overlap by instead of the default 50% of the element height
|
14
|
+
*
|
15
|
+
*/
|
16
|
+
$.fn.simulateDragSortable = function(options) {
|
17
|
+
// build main options before element iteration
|
18
|
+
var opts = $.extend({}, $.fn.simulateDragSortable.defaults, options);
|
19
|
+
|
20
|
+
applyDrag = function(options) {
|
21
|
+
// allow for a drag handle if item is not draggable
|
22
|
+
var that = this,
|
23
|
+
options = options || opts, // default to plugin opts unless options explicitly provided
|
24
|
+
handle = options.handle ? $(this).find(options.handle)[0] : $(this)[0],
|
25
|
+
listItem = options.listItem,
|
26
|
+
placeHolder = options.placeHolder,
|
27
|
+
sibling = $(this),
|
28
|
+
moveCounter = Math.floor(options.move),
|
29
|
+
direction = moveCounter > 0 ? 'down' : 'up',
|
30
|
+
moveVerticalAmount = 0,
|
31
|
+
initialVerticalPosition = 0,
|
32
|
+
extraDrag = !isNaN(parseInt(options.tolerance, 10)) ? function() { return Number(options.tolerance); } : function(obj) { return ($(obj).outerHeight() / 2) + 5; },
|
33
|
+
dragPastBy = 0, // represents the additional amount one drags past an element and bounce back
|
34
|
+
dropOn = options.dropOn ? $(options.dropOn) : false,
|
35
|
+
center = findCenter(handle),
|
36
|
+
x = Math.floor(center.x),
|
37
|
+
y = Math.floor(center.y),
|
38
|
+
mouseUpAfter = (opts.debug ? 2500 : 10);
|
39
|
+
|
40
|
+
if (dropOn) {
|
41
|
+
if (dropOn.length === 0) {
|
42
|
+
if (console && console.log) { console.log('simulate.drag-sortable.js ERROR: Drop on target could not be found'); console.log(options.dropOn); }
|
43
|
+
return;
|
44
|
+
}
|
45
|
+
sibling = dropOn.find('>*:last');
|
46
|
+
moveCounter = -(dropOn.find('>*').length + 1) + (moveCounter + 1); // calculate length of list after this move, use moveCounter as a positive index position in list to reverse back up
|
47
|
+
if (dropOn.offset().top - $(this).offset().top < 0) {
|
48
|
+
// moving to a list above this list, so move to just above top of last item (tried moving to top but JQuery UI wouldn't bite)
|
49
|
+
initialVerticalPosition = sibling.offset().top - $(this).offset().top - extraDrag(this);
|
50
|
+
} else {
|
51
|
+
// moving to a list below this list, so move to bottom and work up (JQuery UI does not trigger new list below unless you move past top item first)
|
52
|
+
initialVerticalPosition = sibling.offset().top - $(this).offset().top - $(this).height();
|
53
|
+
}
|
54
|
+
} else if (moveCounter === 0) {
|
55
|
+
if (console && console.log) { console.log('simulate.drag-sortable.js WARNING: Drag with move set to zero has no effect'); }
|
56
|
+
return;
|
57
|
+
} else {
|
58
|
+
while (moveCounter !== 0) {
|
59
|
+
if (direction === 'down') {
|
60
|
+
if (sibling.next(listItem).length) {
|
61
|
+
sibling = sibling.next(listItem);
|
62
|
+
moveVerticalAmount += sibling.outerHeight();
|
63
|
+
}
|
64
|
+
moveCounter -= 1;
|
65
|
+
} else {
|
66
|
+
if (sibling.prev(listItem).length) {
|
67
|
+
sibling = sibling.prev(listItem);
|
68
|
+
moveVerticalAmount -= sibling.outerHeight();
|
69
|
+
}
|
70
|
+
moveCounter += 1;
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
dispatchEvent(handle, 'mousedown', createEvent('mousedown', handle, { clientX: x, clientY: y }));
|
76
|
+
// simulate drag start
|
77
|
+
dispatchEvent(document, 'mousemove', createEvent('mousemove', document, { clientX: x+1, clientY: y+1 }));
|
78
|
+
|
79
|
+
if (dropOn) {
|
80
|
+
// jump to top or bottom of new list but do it in increments so that JQuery UI registers the drag events
|
81
|
+
slideUpTo(x, y, initialVerticalPosition);
|
82
|
+
|
83
|
+
// reset y position to top or bottom of list and move from there
|
84
|
+
y += initialVerticalPosition;
|
85
|
+
|
86
|
+
// now call regular shift/down in a list
|
87
|
+
options = jQuery.extend(options, { move: moveCounter });
|
88
|
+
delete options.dropOn;
|
89
|
+
|
90
|
+
// add some delays to allow JQuery UI to catch up
|
91
|
+
setTimeout(function() {
|
92
|
+
dispatchEvent(document, 'mousemove', createEvent('mousemove', document, { clientX: x, clientY: y }));
|
93
|
+
}, 5);
|
94
|
+
setTimeout(function() {
|
95
|
+
dispatchEvent(handle, 'mouseup', createEvent('mouseup', handle, { clientX: x, clientY: y }));
|
96
|
+
setTimeout(function() {
|
97
|
+
if (options.move) {
|
98
|
+
applyDrag.call(that, options);
|
99
|
+
}
|
100
|
+
}, 5);
|
101
|
+
}, mouseUpAfter);
|
102
|
+
|
103
|
+
// stop execution as applyDrag has been called again
|
104
|
+
return;
|
105
|
+
}
|
106
|
+
|
107
|
+
// Sortable is using a fixed height placeholder meaning items jump up and down as you drag variable height items into fixed height placeholder
|
108
|
+
placeHolder = placeHolder && $(this).parent().find(placeHolder);
|
109
|
+
|
110
|
+
if (!placeHolder && (direction === 'down')) {
|
111
|
+
// need to move at least as far as this item and or the last sibling
|
112
|
+
if ($(this).outerHeight() > $(sibling).outerHeight()) {
|
113
|
+
moveVerticalAmount += $(this).outerHeight() - $(sibling).outerHeight();
|
114
|
+
}
|
115
|
+
moveVerticalAmount += extraDrag(sibling);
|
116
|
+
dragPastBy += extraDrag(sibling);
|
117
|
+
} else if (direction === 'up') {
|
118
|
+
// move a little extra to ensure item clips into next position
|
119
|
+
moveVerticalAmount -= Math.max(extraDrag(this), 5);
|
120
|
+
} else if (direction === 'down') {
|
121
|
+
// moving down with a place holder
|
122
|
+
if (placeHolder.height() < $(this).height()) {
|
123
|
+
moveVerticalAmount += Math.max(placeHolder.height(), 5);
|
124
|
+
} else {
|
125
|
+
moveVerticalAmount += extraDrag(sibling);
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
if (sibling[0] !== $(this)[0]) {
|
130
|
+
// step through so that the UI controller can determine when to show the placeHolder
|
131
|
+
slideUpTo(x, y, moveVerticalAmount, dragPastBy);
|
132
|
+
} else {
|
133
|
+
if (window.console) {
|
134
|
+
console.log('simulate.drag-sortable.js WARNING: Could not move as at top or bottom already');
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
setTimeout(function() {
|
139
|
+
dispatchEvent(document, 'mousemove', createEvent('mousemove', document, { clientX: x, clientY: y + moveVerticalAmount }));
|
140
|
+
}, 5);
|
141
|
+
setTimeout(function() {
|
142
|
+
dispatchEvent(handle, 'mouseup', createEvent('mouseup', handle, { clientX: x, clientY: y + moveVerticalAmount }));
|
143
|
+
}, mouseUpAfter);
|
144
|
+
};
|
145
|
+
|
146
|
+
// iterate and move each matched element
|
147
|
+
return this.each(applyDrag);
|
148
|
+
};
|
149
|
+
|
150
|
+
// fire mouse events, go half way, then the next half, so small mouse movements near target and big at the start
|
151
|
+
function slideUpTo(x, y, targetOffset, goPastBy) {
|
152
|
+
var moveBy, offset;
|
153
|
+
|
154
|
+
if (!goPastBy) { goPastBy = 0; }
|
155
|
+
if ((targetOffset < 0) && (goPastBy > 0)) { goPastBy = -goPastBy; } // ensure go past is in the direction as often passed in from object height so always positive
|
156
|
+
|
157
|
+
// go forwards including goPastBy
|
158
|
+
for (offset = 0; Math.abs(offset) + 1 < Math.abs(targetOffset + goPastBy); offset += ((targetOffset + goPastBy - offset)/2) ) {
|
159
|
+
dispatchEvent(document, 'mousemove', createEvent('mousemove', document, { clientX: x, clientY: y + Math.ceil(offset) }));
|
160
|
+
}
|
161
|
+
offset = targetOffset + goPastBy;
|
162
|
+
dispatchEvent(document, 'mousemove', createEvent('mousemove', document, { clientX: x, clientY: y + offset }));
|
163
|
+
|
164
|
+
// now bounce back
|
165
|
+
for (; Math.abs(offset) - 1 >= Math.abs(targetOffset); offset += ((targetOffset - offset)/2) ) {
|
166
|
+
dispatchEvent(document, 'mousemove', createEvent('mousemove', document, { clientX: x, clientY: y + Math.ceil(offset) }));
|
167
|
+
}
|
168
|
+
dispatchEvent(document, 'mousemove', createEvent('mousemove', document, { clientX: x, clientY: y + targetOffset }));
|
169
|
+
}
|
170
|
+
|
171
|
+
function createEvent(type, target, options) {
|
172
|
+
var evt;
|
173
|
+
var e = $.extend({
|
174
|
+
target: target,
|
175
|
+
preventDefault: function() { },
|
176
|
+
stopImmediatePropagation: function() { },
|
177
|
+
stopPropagation: function() { },
|
178
|
+
isPropagationStopped: function() { return true; },
|
179
|
+
isImmediatePropagationStopped: function() { return true; },
|
180
|
+
isDefaultPrevented: function() { return true; },
|
181
|
+
bubbles: true,
|
182
|
+
cancelable: (type != "mousemove"),
|
183
|
+
view: window,
|
184
|
+
detail: 0,
|
185
|
+
screenX: 0,
|
186
|
+
screenY: 0,
|
187
|
+
clientX: 0,
|
188
|
+
clientY: 0,
|
189
|
+
ctrlKey: false,
|
190
|
+
altKey: false,
|
191
|
+
shiftKey: false,
|
192
|
+
metaKey: false,
|
193
|
+
button: 0,
|
194
|
+
relatedTarget: undefined
|
195
|
+
}, options || {});
|
196
|
+
|
197
|
+
if ($.isFunction(document.createEvent)) {
|
198
|
+
evt = document.createEvent("MouseEvents");
|
199
|
+
evt.initMouseEvent(type, e.bubbles, e.cancelable, e.view, e.detail,
|
200
|
+
e.screenX, e.screenY, e.clientX, e.clientY,
|
201
|
+
e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
|
202
|
+
e.button, e.relatedTarget || document.body.parentNode);
|
203
|
+
} else if (document.createEventObject) {
|
204
|
+
evt = document.createEventObject();
|
205
|
+
$.extend(evt, e);
|
206
|
+
evt.button = { 0:1, 1:4, 2:2 }[evt.button] || evt.button;
|
207
|
+
}
|
208
|
+
return evt;
|
209
|
+
}
|
210
|
+
|
211
|
+
function dispatchEvent(el, type, evt) {
|
212
|
+
if (el.dispatchEvent) {
|
213
|
+
el.dispatchEvent(evt);
|
214
|
+
} else if (el.fireEvent) {
|
215
|
+
el.fireEvent('on' + type, evt);
|
216
|
+
}
|
217
|
+
return evt;
|
218
|
+
}
|
219
|
+
|
220
|
+
function findCenter(el) {
|
221
|
+
var elm = $(el),
|
222
|
+
o = elm.offset();
|
223
|
+
return {
|
224
|
+
x: o.left + elm.outerWidth() / 2,
|
225
|
+
y: o.top + elm.outerHeight() / 2
|
226
|
+
};
|
227
|
+
}
|
228
|
+
|
229
|
+
//
|
230
|
+
// plugin defaults
|
231
|
+
//
|
232
|
+
$.fn.simulateDragSortable.defaults = {
|
233
|
+
move: 0
|
234
|
+
};
|
235
|
+
})(jQuery);
|
@@ -1,43 +1,66 @@
|
|
1
|
-
RSpec.describe ActiveAdmin::SortableTable, 'Drag-and-Drop', type: :feature do
|
1
|
+
RSpec.describe ActiveAdmin::SortableTable, 'Drag-and-Drop', type: :feature, js: true do
|
2
2
|
before do
|
3
3
|
Category.create!
|
4
4
|
Category.create!
|
5
5
|
Category.create!
|
6
6
|
end
|
7
7
|
|
8
|
-
def
|
8
|
+
def ordered_ids
|
9
9
|
Category.order(:position).pluck(:id)
|
10
10
|
end
|
11
11
|
|
12
12
|
context 'first page' do
|
13
|
-
|
14
|
-
expect(
|
13
|
+
before do
|
14
|
+
expect(ordered_ids).to eq([1, 2, 3])
|
15
15
|
|
16
16
|
visit admin_categories_path
|
17
17
|
|
18
|
-
expect(
|
18
|
+
expect(visible_ids).to eq([1, 2, 3])
|
19
19
|
expect(visible_positions).to eq([1, 2, 3])
|
20
|
+
end
|
20
21
|
|
21
|
-
|
22
|
+
context 'move element up' do
|
23
|
+
it 'to the top position' do
|
24
|
+
move_up(3, by: 2)
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
expect(visible_ids).to eq([3, 1, 2])
|
27
|
+
expect(visible_positions).to eq([1, 2, 3])
|
28
|
+
expect(ordered_ids).to eq([3, 1, 2])
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'to the middle of the table' do
|
32
|
+
move_up(3, by: 1)
|
33
|
+
|
34
|
+
expect(visible_ids).to eq([1, 3, 2])
|
35
|
+
expect(visible_positions).to eq([1, 2, 3])
|
36
|
+
expect(ordered_ids).to eq([1, 3, 2])
|
37
|
+
end
|
26
38
|
end
|
27
39
|
|
28
|
-
|
29
|
-
|
40
|
+
context 'move element down' do
|
41
|
+
it 'to the bottom of the table' do
|
42
|
+
move_down(1, by: 2)
|
30
43
|
|
31
|
-
|
44
|
+
expect(visible_ids).to eq([2, 3, 1])
|
45
|
+
expect(visible_positions).to eq([1, 2, 3])
|
46
|
+
expect(ordered_ids).to eq([2, 3, 1])
|
47
|
+
end
|
32
48
|
|
33
|
-
|
34
|
-
|
49
|
+
it 'to the middle of the table' do
|
50
|
+
move_down(2, by: 1)
|
51
|
+
|
52
|
+
expect(visible_ids).to eq([1, 3, 2])
|
53
|
+
expect(visible_positions).to eq([1, 2, 3])
|
54
|
+
expect(ordered_ids).to eq([1, 3, 2])
|
55
|
+
end
|
56
|
+
end
|
35
57
|
|
36
|
-
|
58
|
+
it 'can not drug not by handle' do
|
59
|
+
move_up(3, by: 2, use_handle: false)
|
37
60
|
|
38
|
-
expect(
|
61
|
+
expect(visible_ids).to eq([1, 2, 3])
|
39
62
|
expect(visible_positions).to eq([1, 2, 3])
|
40
|
-
expect(
|
63
|
+
expect(ordered_ids).to eq([1, 2, 3])
|
41
64
|
end
|
42
65
|
end
|
43
66
|
|
@@ -48,40 +71,63 @@ RSpec.describe ActiveAdmin::SortableTable, 'Drag-and-Drop', type: :feature do
|
|
48
71
|
Category.create!
|
49
72
|
end
|
50
73
|
|
51
|
-
|
52
|
-
expect(
|
74
|
+
before do
|
75
|
+
expect(ordered_ids).to eq([1, 2, 3, 4, 5, 6])
|
53
76
|
|
54
77
|
visit admin_categories_path(page: 2)
|
55
78
|
|
56
|
-
expect(
|
79
|
+
expect(visible_ids).to eq([4, 5, 6])
|
57
80
|
expect(visible_positions).to eq([4, 5, 6])
|
81
|
+
end
|
58
82
|
|
59
|
-
|
83
|
+
context 'move element up' do
|
84
|
+
it 'to the top position' do
|
85
|
+
move_up(6, by: 2)
|
60
86
|
|
61
|
-
|
62
|
-
|
63
|
-
|
87
|
+
expect(visible_ids).to eq([6, 4, 5])
|
88
|
+
expect(visible_positions).to eq([4, 5, 6])
|
89
|
+
expect(ordered_ids).to eq([1, 2, 3, 6, 4, 5])
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'to the middle of the table' do
|
93
|
+
move_up(6, by: 1)
|
94
|
+
|
95
|
+
expect(visible_ids).to eq([4, 6, 5])
|
96
|
+
expect(visible_positions).to eq([4, 5, 6])
|
97
|
+
expect(ordered_ids).to eq([1, 2, 3, 4, 6, 5])
|
98
|
+
end
|
64
99
|
end
|
65
100
|
|
66
|
-
|
67
|
-
|
101
|
+
context 'move element down' do
|
102
|
+
it 'to the bottom of the table' do
|
103
|
+
move_down(4, by: 2)
|
68
104
|
|
69
|
-
|
105
|
+
expect(visible_ids).to eq([5, 6, 4])
|
106
|
+
expect(visible_positions).to eq([4, 5, 6])
|
107
|
+
expect(ordered_ids).to eq([1, 2, 3, 5, 6, 4])
|
108
|
+
end
|
70
109
|
|
71
|
-
|
72
|
-
|
110
|
+
it 'to the middle of the table' do
|
111
|
+
move_down(4, by: 1)
|
73
112
|
|
74
|
-
|
113
|
+
expect(visible_ids).to eq([5, 4, 6])
|
114
|
+
expect(visible_positions).to eq([4, 5, 6])
|
115
|
+
expect(ordered_ids).to eq([1, 2, 3, 5, 4, 6])
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'can not drug not by handle' do
|
120
|
+
move_down(6, by: 2, use_handle: false)
|
75
121
|
|
76
|
-
expect(
|
122
|
+
expect(visible_ids).to eq([4, 5, 6])
|
77
123
|
expect(visible_positions).to eq([4, 5, 6])
|
78
|
-
expect(
|
124
|
+
expect(ordered_ids).to eq([1, 2, 3, 4, 5, 6])
|
79
125
|
end
|
80
126
|
end
|
81
127
|
|
82
128
|
private
|
83
129
|
|
84
|
-
def
|
130
|
+
def visible_ids
|
85
131
|
all('.ui-sortable .col-id').map(&:text).map(&:to_i)
|
86
132
|
end
|
87
133
|
|
@@ -89,14 +135,24 @@ RSpec.describe ActiveAdmin::SortableTable, 'Drag-and-Drop', type: :feature do
|
|
89
135
|
all('.ui-sortable .col-position').map(&:text).map(&:to_i)
|
90
136
|
end
|
91
137
|
|
92
|
-
def
|
93
|
-
drag_element(element_id,
|
138
|
+
def move_up(element_id, by: 1, use_handle: true)
|
139
|
+
drag_element(element_id, by: -by, use_handle: use_handle)
|
94
140
|
end
|
95
141
|
|
96
|
-
def
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
142
|
+
def move_down(element_id, by: 1, use_handle: true)
|
143
|
+
drag_element(element_id, by: by, use_handle: use_handle)
|
144
|
+
end
|
145
|
+
|
146
|
+
def drag_element(element_id, by:, use_handle:)
|
147
|
+
if use_handle
|
148
|
+
page.execute_script(<<-JS)
|
149
|
+
$("#category_#{element_id}").simulateDragSortable({ move: #{by}, handle: ".handle" })
|
150
|
+
JS
|
151
|
+
else
|
152
|
+
page.execute_script(<<-JS)
|
153
|
+
$("#category_#{element_id}").simulateDragSortable({ move: #{by} })
|
154
|
+
JS
|
155
|
+
end
|
156
|
+
sleep 0.5
|
101
157
|
end
|
102
158
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
RSpec.describe ActiveAdmin::SortableTable, 'Move to top', type: :feature do
|
1
|
+
RSpec.describe ActiveAdmin::SortableTable, 'Move to top', type: :feature, js: true do
|
2
2
|
before do
|
3
3
|
Category.create!
|
4
4
|
Category.create!
|
@@ -6,39 +6,47 @@ RSpec.describe ActiveAdmin::SortableTable, 'Move to top', type: :feature do
|
|
6
6
|
Category.create!
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
9
|
+
def ordered_ids
|
10
10
|
Category.order(:position).pluck(:id)
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
expect(
|
13
|
+
before do
|
14
|
+
expect(ordered_ids).to eq([1, 2, 3, 4])
|
15
15
|
|
16
16
|
# Initially only one element on the second page
|
17
17
|
visit admin_categories_path(page: 2)
|
18
18
|
|
19
|
-
expect(
|
19
|
+
expect(visible_ids).to contain_exactly(4)
|
20
|
+
expect(visible_positions).to contain_exactly(4)
|
21
|
+
end
|
20
22
|
|
23
|
+
it 'push element to top by clicking "move to top"' do
|
21
24
|
# When I push "move to top" button
|
22
25
|
move_to_top(4)
|
23
26
|
|
24
27
|
# The last element from the previous page should be shown
|
25
28
|
# save_and_open_page
|
26
|
-
expect(
|
29
|
+
expect(visible_ids).to contain_exactly(3)
|
27
30
|
|
28
31
|
# And when I visit previous page
|
29
32
|
visit admin_categories_path(page: 1)
|
30
33
|
|
31
34
|
# I should see pushed elenent on the top
|
32
|
-
expect(
|
33
|
-
expect(
|
35
|
+
expect(visible_ids).to eq([4, 1, 2])
|
36
|
+
expect(visible_positions).to eq([1, 2, 3])
|
37
|
+
expect(ordered_ids).to eq([4, 1, 2, 3])
|
34
38
|
end
|
35
39
|
|
36
40
|
private
|
37
41
|
|
38
|
-
def
|
42
|
+
def visible_ids
|
39
43
|
all('.ui-sortable .col-id').map(&:text).map(&:to_i)
|
40
44
|
end
|
41
45
|
|
46
|
+
def visible_positions
|
47
|
+
all('.ui-sortable .col-position').map(&:text).map(&:to_i)
|
48
|
+
end
|
49
|
+
|
42
50
|
def move_to_top(element_id)
|
43
51
|
within "#category_#{element_id}" do
|
44
52
|
first('.move_to_top').click
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activeadmin_sortable_table
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam McCrea
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2015-10-
|
13
|
+
date: 2015-10-23 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activeadmin
|
@@ -172,7 +172,7 @@ files:
|
|
172
172
|
- spec/dummy/app/assets/images/.keep
|
173
173
|
- spec/dummy/app/assets/javascripts/active_admin.js.coffee
|
174
174
|
- spec/dummy/app/assets/javascripts/application.js
|
175
|
-
- spec/dummy/app/assets/javascripts/jquery.simulate.js
|
175
|
+
- spec/dummy/app/assets/javascripts/jquery.simulate.drag-sortable.js
|
176
176
|
- spec/dummy/app/assets/stylesheets/active_admin.scss
|
177
177
|
- spec/dummy/app/assets/stylesheets/application.css
|
178
178
|
- spec/dummy/app/controllers/application_controller.rb
|
@@ -242,7 +242,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
242
242
|
version: '0'
|
243
243
|
requirements: []
|
244
244
|
rubyforge_project:
|
245
|
-
rubygems_version: 2.4.
|
245
|
+
rubygems_version: 2.4.8
|
246
246
|
signing_key:
|
247
247
|
specification_version: 4
|
248
248
|
summary: Drag and drop reordering interface for ActiveAdmin tables
|
@@ -254,7 +254,7 @@ test_files:
|
|
254
254
|
- spec/dummy/app/assets/images/.keep
|
255
255
|
- spec/dummy/app/assets/javascripts/active_admin.js.coffee
|
256
256
|
- spec/dummy/app/assets/javascripts/application.js
|
257
|
-
- spec/dummy/app/assets/javascripts/jquery.simulate.js
|
257
|
+
- spec/dummy/app/assets/javascripts/jquery.simulate.drag-sortable.js
|
258
258
|
- spec/dummy/app/assets/stylesheets/active_admin.scss
|
259
259
|
- spec/dummy/app/assets/stylesheets/application.css
|
260
260
|
- spec/dummy/app/controllers/application_controller.rb
|
@@ -1,314 +0,0 @@
|
|
1
|
-
/*!
|
2
|
-
* jQuery Simulate v@VERSION - simulate browser mouse and keyboard events
|
3
|
-
* https://github.com/jquery/jquery-simulate
|
4
|
-
*
|
5
|
-
* Copyright 2012 jQuery Foundation and other contributors
|
6
|
-
* Released under the MIT license.
|
7
|
-
* http://jquery.org/license
|
8
|
-
*
|
9
|
-
* Date: @DATE
|
10
|
-
*/
|
11
|
-
|
12
|
-
;(function( $, undefined ) {
|
13
|
-
|
14
|
-
var rkeyEvent = /^key/,
|
15
|
-
rmouseEvent = /^(?:mouse|contextmenu)|click/;
|
16
|
-
|
17
|
-
$.fn.simulate = function( type, options ) {
|
18
|
-
return this.each(function() {
|
19
|
-
new $.simulate( this, type, options );
|
20
|
-
});
|
21
|
-
};
|
22
|
-
|
23
|
-
$.simulate = function( elem, type, options ) {
|
24
|
-
var method = $.camelCase( "simulate-" + type );
|
25
|
-
|
26
|
-
this.target = elem;
|
27
|
-
this.options = options;
|
28
|
-
|
29
|
-
if ( this[ method ] ) {
|
30
|
-
this[ method ]();
|
31
|
-
} else {
|
32
|
-
this.simulateEvent( elem, type, options );
|
33
|
-
}
|
34
|
-
};
|
35
|
-
|
36
|
-
$.extend( $.simulate, {
|
37
|
-
|
38
|
-
keyCode: {
|
39
|
-
BACKSPACE: 8,
|
40
|
-
COMMA: 188,
|
41
|
-
DELETE: 46,
|
42
|
-
DOWN: 40,
|
43
|
-
END: 35,
|
44
|
-
ENTER: 13,
|
45
|
-
ESCAPE: 27,
|
46
|
-
HOME: 36,
|
47
|
-
LEFT: 37,
|
48
|
-
NUMPAD_ADD: 107,
|
49
|
-
NUMPAD_DECIMAL: 110,
|
50
|
-
NUMPAD_DIVIDE: 111,
|
51
|
-
NUMPAD_ENTER: 108,
|
52
|
-
NUMPAD_MULTIPLY: 106,
|
53
|
-
NUMPAD_SUBTRACT: 109,
|
54
|
-
PAGE_DOWN: 34,
|
55
|
-
PAGE_UP: 33,
|
56
|
-
PERIOD: 190,
|
57
|
-
RIGHT: 39,
|
58
|
-
SPACE: 32,
|
59
|
-
TAB: 9,
|
60
|
-
UP: 38
|
61
|
-
},
|
62
|
-
|
63
|
-
buttonCode: {
|
64
|
-
LEFT: 0,
|
65
|
-
MIDDLE: 1,
|
66
|
-
RIGHT: 2
|
67
|
-
}
|
68
|
-
});
|
69
|
-
|
70
|
-
$.extend( $.simulate.prototype, {
|
71
|
-
|
72
|
-
simulateEvent: function( elem, type, options ) {
|
73
|
-
var event = this.createEvent( type, options );
|
74
|
-
this.dispatchEvent( elem, type, event, options );
|
75
|
-
},
|
76
|
-
|
77
|
-
createEvent: function( type, options ) {
|
78
|
-
if ( rkeyEvent.test( type ) ) {
|
79
|
-
return this.keyEvent( type, options );
|
80
|
-
}
|
81
|
-
|
82
|
-
if ( rmouseEvent.test( type ) ) {
|
83
|
-
return this.mouseEvent( type, options );
|
84
|
-
}
|
85
|
-
},
|
86
|
-
|
87
|
-
mouseEvent: function( type, options ) {
|
88
|
-
var event, eventDoc, doc, body;
|
89
|
-
options = $.extend({
|
90
|
-
bubbles: true,
|
91
|
-
cancelable: (type !== "mousemove"),
|
92
|
-
view: window,
|
93
|
-
detail: 0,
|
94
|
-
screenX: 0,
|
95
|
-
screenY: 0,
|
96
|
-
clientX: 1,
|
97
|
-
clientY: 1,
|
98
|
-
ctrlKey: false,
|
99
|
-
altKey: false,
|
100
|
-
shiftKey: false,
|
101
|
-
metaKey: false,
|
102
|
-
button: 0,
|
103
|
-
relatedTarget: undefined
|
104
|
-
}, options );
|
105
|
-
|
106
|
-
if ( document.createEvent ) {
|
107
|
-
event = document.createEvent( "MouseEvents" );
|
108
|
-
event.initMouseEvent( type, options.bubbles, options.cancelable,
|
109
|
-
options.view, options.detail,
|
110
|
-
options.screenX, options.screenY, options.clientX, options.clientY,
|
111
|
-
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
|
112
|
-
options.button, options.relatedTarget || document.body.parentNode );
|
113
|
-
|
114
|
-
// IE 9+ creates events with pageX and pageY set to 0.
|
115
|
-
// Trying to modify the properties throws an error,
|
116
|
-
// so we define getters to return the correct values.
|
117
|
-
if ( event.pageX === 0 && event.pageY === 0 && Object.defineProperty ) {
|
118
|
-
eventDoc = event.relatedTarget.ownerDocument || document;
|
119
|
-
doc = eventDoc.documentElement;
|
120
|
-
body = eventDoc.body;
|
121
|
-
|
122
|
-
Object.defineProperty( event, "pageX", {
|
123
|
-
get: function() {
|
124
|
-
return options.clientX +
|
125
|
-
( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
|
126
|
-
( doc && doc.clientLeft || body && body.clientLeft || 0 );
|
127
|
-
}
|
128
|
-
});
|
129
|
-
Object.defineProperty( event, "pageY", {
|
130
|
-
get: function() {
|
131
|
-
return options.clientY +
|
132
|
-
( doc && doc.scrollTop || body && body.scrollTop || 0 ) -
|
133
|
-
( doc && doc.clientTop || body && body.clientTop || 0 );
|
134
|
-
}
|
135
|
-
});
|
136
|
-
}
|
137
|
-
} else if ( document.createEventObject ) {
|
138
|
-
event = document.createEventObject();
|
139
|
-
$.extend( event, options );
|
140
|
-
// standards event.button uses constants defined here: http://msdn.microsoft.com/en-us/library/ie/ff974877(v=vs.85).aspx
|
141
|
-
// old IE event.button uses constants defined here: http://msdn.microsoft.com/en-us/library/ie/ms533544(v=vs.85).aspx
|
142
|
-
// so we actually need to map the standard back to oldIE
|
143
|
-
event.button = {
|
144
|
-
0: 1,
|
145
|
-
1: 4,
|
146
|
-
2: 2
|
147
|
-
}[ event.button ] || event.button;
|
148
|
-
}
|
149
|
-
|
150
|
-
return event;
|
151
|
-
},
|
152
|
-
|
153
|
-
keyEvent: function( type, options ) {
|
154
|
-
var event;
|
155
|
-
options = $.extend({
|
156
|
-
bubbles: true,
|
157
|
-
cancelable: true,
|
158
|
-
view: window,
|
159
|
-
ctrlKey: false,
|
160
|
-
altKey: false,
|
161
|
-
shiftKey: false,
|
162
|
-
metaKey: false,
|
163
|
-
keyCode: 0,
|
164
|
-
charCode: undefined
|
165
|
-
}, options );
|
166
|
-
|
167
|
-
if ( document.createEvent ) {
|
168
|
-
try {
|
169
|
-
event = document.createEvent( "KeyEvents" );
|
170
|
-
event.initKeyEvent( type, options.bubbles, options.cancelable, options.view,
|
171
|
-
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
|
172
|
-
options.keyCode, options.charCode );
|
173
|
-
// initKeyEvent throws an exception in WebKit
|
174
|
-
// see: http://stackoverflow.com/questions/6406784/initkeyevent-keypress-only-works-in-firefox-need-a-cross-browser-solution
|
175
|
-
// and also https://bugs.webkit.org/show_bug.cgi?id=13368
|
176
|
-
// fall back to a generic event until we decide to implement initKeyboardEvent
|
177
|
-
} catch( err ) {
|
178
|
-
event = document.createEvent( "Events" );
|
179
|
-
event.initEvent( type, options.bubbles, options.cancelable );
|
180
|
-
$.extend( event, {
|
181
|
-
view: options.view,
|
182
|
-
ctrlKey: options.ctrlKey,
|
183
|
-
altKey: options.altKey,
|
184
|
-
shiftKey: options.shiftKey,
|
185
|
-
metaKey: options.metaKey,
|
186
|
-
keyCode: options.keyCode,
|
187
|
-
charCode: options.charCode
|
188
|
-
});
|
189
|
-
}
|
190
|
-
} else if ( document.createEventObject ) {
|
191
|
-
event = document.createEventObject();
|
192
|
-
$.extend( event, options );
|
193
|
-
}
|
194
|
-
|
195
|
-
if ( !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ) || (({}).toString.call( window.opera ) === "[object Opera]") ) {
|
196
|
-
event.keyCode = (options.charCode > 0) ? options.charCode : options.keyCode;
|
197
|
-
event.charCode = undefined;
|
198
|
-
}
|
199
|
-
|
200
|
-
return event;
|
201
|
-
},
|
202
|
-
|
203
|
-
dispatchEvent: function( elem, type, event ) {
|
204
|
-
if ( elem[ type ] ) {
|
205
|
-
elem[ type ]();
|
206
|
-
} else if ( elem.dispatchEvent ) {
|
207
|
-
elem.dispatchEvent( event );
|
208
|
-
} else if ( elem.fireEvent ) {
|
209
|
-
elem.fireEvent( "on" + type, event );
|
210
|
-
}
|
211
|
-
},
|
212
|
-
|
213
|
-
simulateFocus: function() {
|
214
|
-
var focusinEvent,
|
215
|
-
triggered = false,
|
216
|
-
element = $( this.target );
|
217
|
-
|
218
|
-
function trigger() {
|
219
|
-
triggered = true;
|
220
|
-
}
|
221
|
-
|
222
|
-
element.bind( "focus", trigger );
|
223
|
-
element[ 0 ].focus();
|
224
|
-
|
225
|
-
if ( !triggered ) {
|
226
|
-
focusinEvent = $.Event( "focusin" );
|
227
|
-
focusinEvent.preventDefault();
|
228
|
-
element.trigger( focusinEvent );
|
229
|
-
element.triggerHandler( "focus" );
|
230
|
-
}
|
231
|
-
element.unbind( "focus", trigger );
|
232
|
-
},
|
233
|
-
|
234
|
-
simulateBlur: function() {
|
235
|
-
var focusoutEvent,
|
236
|
-
triggered = false,
|
237
|
-
element = $( this.target );
|
238
|
-
|
239
|
-
function trigger() {
|
240
|
-
triggered = true;
|
241
|
-
}
|
242
|
-
|
243
|
-
element.bind( "blur", trigger );
|
244
|
-
element[ 0 ].blur();
|
245
|
-
|
246
|
-
// blur events are async in IE
|
247
|
-
setTimeout(function() {
|
248
|
-
// IE won't let the blur occur if the window is inactive
|
249
|
-
if ( element[ 0 ].ownerDocument.activeElement === element[ 0 ] ) {
|
250
|
-
element[ 0 ].ownerDocument.body.focus();
|
251
|
-
}
|
252
|
-
|
253
|
-
// Firefox won't trigger events if the window is inactive
|
254
|
-
// IE doesn't trigger events if we had to manually focus the body
|
255
|
-
if ( !triggered ) {
|
256
|
-
focusoutEvent = $.Event( "focusout" );
|
257
|
-
focusoutEvent.preventDefault();
|
258
|
-
element.trigger( focusoutEvent );
|
259
|
-
element.triggerHandler( "blur" );
|
260
|
-
}
|
261
|
-
element.unbind( "blur", trigger );
|
262
|
-
}, 1 );
|
263
|
-
}
|
264
|
-
});
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
/** complex events **/
|
269
|
-
|
270
|
-
function findCenter( elem ) {
|
271
|
-
var offset,
|
272
|
-
document = $( elem.ownerDocument );
|
273
|
-
elem = $( elem );
|
274
|
-
offset = elem.offset();
|
275
|
-
|
276
|
-
return {
|
277
|
-
x: offset.left + elem.outerWidth() / 2 - document.scrollLeft(),
|
278
|
-
y: offset.top + elem.outerHeight() / 2 - document.scrollTop()
|
279
|
-
};
|
280
|
-
}
|
281
|
-
|
282
|
-
$.extend( $.simulate.prototype, {
|
283
|
-
simulateDrag: function() {
|
284
|
-
var i = 0,
|
285
|
-
target = this.target,
|
286
|
-
options = this.options,
|
287
|
-
center = findCenter( target ),
|
288
|
-
x = Math.floor( center.x ),
|
289
|
-
y = Math.floor( center.y ),
|
290
|
-
dx = options.dx || 0,
|
291
|
-
dy = options.dy || 0,
|
292
|
-
moves = options.moves || 3,
|
293
|
-
coord = { clientX: x, clientY: y };
|
294
|
-
|
295
|
-
this.simulateEvent( target, "mousedown", coord );
|
296
|
-
|
297
|
-
for ( ; i < moves ; i++ ) {
|
298
|
-
x += dx / moves;
|
299
|
-
y += dy / moves;
|
300
|
-
|
301
|
-
coord = {
|
302
|
-
clientX: Math.round( x ),
|
303
|
-
clientY: Math.round( y )
|
304
|
-
};
|
305
|
-
|
306
|
-
this.simulateEvent( document, "mousemove", coord );
|
307
|
-
}
|
308
|
-
|
309
|
-
this.simulateEvent( target, "mouseup", coord );
|
310
|
-
this.simulateEvent( target, "click", coord );
|
311
|
-
}
|
312
|
-
});
|
313
|
-
|
314
|
-
})( jQuery );
|