the_sortable_tree 0.0.1 → 1.0.0
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.
- data/README.md +67 -0
- data/app/assets/images/iconza/blue/add.png +0 -0
- data/app/assets/images/iconza/blue/delete.png +0 -0
- data/app/assets/images/iconza/blue/down.png +0 -0
- data/app/assets/images/iconza/blue/downloads_folder.png +0 -0
- data/app/assets/images/iconza/blue/edit.png +0 -0
- data/app/assets/images/iconza/blue/move.png +0 -0
- data/app/assets/images/iconza/blue/up.png +0 -0
- data/app/assets/images/iconza/gray/add.png +0 -0
- data/app/assets/images/iconza/gray/delete.png +0 -0
- data/app/assets/images/iconza/gray/down.png +0 -0
- data/app/assets/images/iconza/gray/edit.png +0 -0
- data/app/assets/images/iconza/gray/lock.png +0 -0
- data/app/assets/images/iconza/gray/mail.png +0 -0
- data/app/assets/images/iconza/gray/push_pin.png +0 -0
- data/app/assets/images/iconza/gray/up.png +0 -0
- data/app/assets/images/iconza/red/add.png +0 -0
- data/app/assets/images/iconza/red/delete.png +0 -0
- data/app/assets/images/iconza/red/down.png +0 -0
- data/app/assets/images/iconza/red/edit.png +0 -0
- data/app/assets/images/iconza/red/newspaper.png +0 -0
- data/app/assets/images/iconza/red/trash.png +0 -0
- data/app/assets/images/iconza/red/up.png +0 -0
- data/app/assets/images/iconza/red/zoom.png +0 -0
- data/app/assets/javascripts/jquery.ui.nestedSortable.js +356 -0
- data/app/assets/stylesheets/the_sortable_tree.css +114 -0
- data/app/controllers/the_sortable_tree_controller.rb +67 -0
- data/app/helpers/the_sortable_tree_helper.rb +107 -0
- data/app/views/the_sortable_tree/_controls.html.haml +16 -0
- data/app/views/the_sortable_tree/_js_init_sortable_tree.html.haml +17 -0
- data/app/views/the_sortable_tree/_js_on_update_tree.html.haml +12 -0
- data/app/views/the_sortable_tree/_js_rebuild_ajax.html.haml +21 -0
- data/app/views/the_sortable_tree/_link.html.haml +10 -0
- data/app/views/the_sortable_tree/_nested_set.html.haml +1 -0
- data/app/views/the_sortable_tree/_nested_set_item.html.haml +3 -0
- data/app/views/the_sortable_tree/_new.html.haml +3 -0
- data/app/views/the_sortable_tree/_tree.html.haml +6 -0
- data/lib/generators/the_sortable_tree/views_generator.rb +25 -0
- data/lib/the_sortable_tree/engine.rb +10 -0
- data/lib/the_sortable_tree/version.rb +1 -1
- data/the_sortable_tree.gemspec +2 -2
- metadata +45 -6
data/README.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
### TheSortableTree
|
2
|
+
|
3
|
+
GUI for nested_set gem. Rails 3+
|
4
|
+
|
5
|
+
**Engine Based**
|
6
|
+
|
7
|
+
### Install
|
8
|
+
|
9
|
+
gem 'the_sortable_tree'
|
10
|
+
|
11
|
+
bundle
|
12
|
+
|
13
|
+
### Require
|
14
|
+
|
15
|
+
1. gem 'nested_set'
|
16
|
+
2. gem 'haml'
|
17
|
+
3. JQuery UI
|
18
|
+
|
19
|
+
### Extend you Model
|
20
|
+
|
21
|
+
``` ruby
|
22
|
+
# SCOPES FOR SORTABLE NESTED SET
|
23
|
+
scope :nested_set, order('lft ASC')
|
24
|
+
scope :reversed_nested_set, order('lft DESC')
|
25
|
+
```
|
26
|
+
|
27
|
+
### Extend you Controller
|
28
|
+
|
29
|
+
``` ruby
|
30
|
+
include TheSortableTreeController::Rebuild
|
31
|
+
```
|
32
|
+
|
33
|
+
### Extend you Routes
|
34
|
+
|
35
|
+
``` ruby
|
36
|
+
resources :elements do
|
37
|
+
collection do
|
38
|
+
get :manage
|
39
|
+
post :rebuild
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
### Find you tree
|
45
|
+
|
46
|
+
``` ruby
|
47
|
+
def manage
|
48
|
+
@elements = Element.all.nested_set
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
### Render you tree with TheSortableTree
|
53
|
+
|
54
|
+
``` ruby
|
55
|
+
- content_for :css do
|
56
|
+
= stylesheet_link_tag 'the_sortable_tree', :media => :screen
|
57
|
+
- content_for :js do
|
58
|
+
= javascript_include_tag 'jquery.ui.nestedSortable'
|
59
|
+
|
60
|
+
= sortable_tree @elements, :klass => :element, :rebuild_url => rebuild_elements_path
|
61
|
+
```
|
62
|
+
|
63
|
+
### Customize
|
64
|
+
|
65
|
+
``` ruby
|
66
|
+
rails g the_sortable_tree:views elements
|
67
|
+
```
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,356 @@
|
|
1
|
+
/*
|
2
|
+
* jQuery UI Nested Sortable
|
3
|
+
* v 1.3.4 / 28 apr 2011
|
4
|
+
* http://mjsarfatti.com/sandbox/nestedSortable
|
5
|
+
*
|
6
|
+
* Depends:
|
7
|
+
* jquery.ui.sortable.js 1.8+
|
8
|
+
*
|
9
|
+
* License CC BY-SA 3.0
|
10
|
+
* Copyright 2010-2011, Manuele J Sarfatti
|
11
|
+
*/
|
12
|
+
|
13
|
+
(function($) {
|
14
|
+
|
15
|
+
$.widget("ui.nestedSortable", $.extend({}, $.ui.sortable.prototype, {
|
16
|
+
|
17
|
+
options: {
|
18
|
+
tabSize: 20,
|
19
|
+
disableNesting: 'ui-nestedSortable-no-nesting',
|
20
|
+
errorClass: 'ui-nestedSortable-error',
|
21
|
+
listType: 'ol',
|
22
|
+
maxLevels: 0,
|
23
|
+
noJumpFix: 0
|
24
|
+
},
|
25
|
+
|
26
|
+
_create: function(){
|
27
|
+
if (this.noJumpFix == false)
|
28
|
+
this.element.height(this.element.height());
|
29
|
+
this.element.data('sortable', this.element.data('nestedSortable'));
|
30
|
+
return $.ui.sortable.prototype._create.apply(this, arguments);
|
31
|
+
},
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
_mouseDrag: function(event) {
|
36
|
+
|
37
|
+
//Compute the helpers position
|
38
|
+
this.position = this._generatePosition(event);
|
39
|
+
this.positionAbs = this._convertPositionTo("absolute");
|
40
|
+
|
41
|
+
if (!this.lastPositionAbs) {
|
42
|
+
this.lastPositionAbs = this.positionAbs;
|
43
|
+
}
|
44
|
+
|
45
|
+
//Do scrolling
|
46
|
+
if(this.options.scroll) {
|
47
|
+
var o = this.options, scrolled = false;
|
48
|
+
if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
|
49
|
+
|
50
|
+
if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
|
51
|
+
this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
|
52
|
+
else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
|
53
|
+
this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
|
54
|
+
|
55
|
+
if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
|
56
|
+
this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
|
57
|
+
else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
|
58
|
+
this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
|
59
|
+
|
60
|
+
} else {
|
61
|
+
|
62
|
+
if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
|
63
|
+
scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
|
64
|
+
else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
|
65
|
+
scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
|
66
|
+
|
67
|
+
if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
|
68
|
+
scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
|
69
|
+
else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
|
70
|
+
scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
|
71
|
+
|
72
|
+
}
|
73
|
+
|
74
|
+
if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
|
75
|
+
$.ui.ddmanager.prepareOffsets(this, event);
|
76
|
+
}
|
77
|
+
|
78
|
+
//Regenerate the absolute position used for position checks
|
79
|
+
this.positionAbs = this._convertPositionTo("absolute");
|
80
|
+
|
81
|
+
//Set the helper position
|
82
|
+
if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
|
83
|
+
if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
|
84
|
+
|
85
|
+
//Rearrange
|
86
|
+
for (var i = this.items.length - 1; i >= 0; i--) {
|
87
|
+
|
88
|
+
//Cache variables and intersection, continue if no intersection
|
89
|
+
var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
|
90
|
+
if (!intersection) continue;
|
91
|
+
|
92
|
+
if(itemElement != this.currentItem[0] //cannot intersect with itself
|
93
|
+
&& this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
|
94
|
+
&& !$.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
|
95
|
+
&& (this.options.type == 'semi-dynamic' ? !$.contains(this.element[0], itemElement) : true)
|
96
|
+
//&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
|
97
|
+
) {
|
98
|
+
|
99
|
+
this.direction = intersection == 1 ? "down" : "up";
|
100
|
+
|
101
|
+
if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
|
102
|
+
this._rearrange(event, item);
|
103
|
+
} else {
|
104
|
+
break;
|
105
|
+
}
|
106
|
+
|
107
|
+
// Clear emtpy ul's/ol's
|
108
|
+
this._clearEmpty(itemElement);
|
109
|
+
|
110
|
+
this._trigger("change", event, this._uiHash());
|
111
|
+
break;
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
var parentItem = (this.placeholder[0].parentNode.parentNode && $(this.placeholder[0].parentNode.parentNode).closest('.ui-sortable').length) ? $(this.placeholder[0].parentNode.parentNode) : null;
|
116
|
+
var level = this._getLevel(this.placeholder);
|
117
|
+
var childLevels = this._getChildLevels(this.helper);
|
118
|
+
var previousItem = this.placeholder[0].previousSibling ? $(this.placeholder[0].previousSibling) : null;
|
119
|
+
if (previousItem != null) {
|
120
|
+
while (previousItem[0].nodeName.toLowerCase() != 'li' || previousItem[0] == this.currentItem[0]) {
|
121
|
+
if (previousItem[0].previousSibling) {
|
122
|
+
previousItem = $(previousItem[0].previousSibling);
|
123
|
+
} else {
|
124
|
+
previousItem = null;
|
125
|
+
break;
|
126
|
+
}
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
newList = document.createElement(o.listType);
|
131
|
+
|
132
|
+
this.beyondMaxLevels = 0;
|
133
|
+
|
134
|
+
// If the item is moved to the left, send it to its parent level
|
135
|
+
if (parentItem != null && this.positionAbs.left < parentItem.offset().left) {
|
136
|
+
parentItem.after(this.placeholder[0]);
|
137
|
+
this._clearEmpty(parentItem[0]);
|
138
|
+
this._trigger("change", event, this._uiHash());
|
139
|
+
}
|
140
|
+
// If the item is below another one and is moved to the right, make it a children of it
|
141
|
+
else if (previousItem != null && this.positionAbs.left > previousItem.offset().left + o.tabSize) {
|
142
|
+
this._isAllowed(previousItem, level+childLevels+1);
|
143
|
+
if (!previousItem.children(o.listType).length) {
|
144
|
+
previousItem[0].appendChild(newList);
|
145
|
+
}
|
146
|
+
previousItem.children(o.listType)[0].appendChild(this.placeholder[0]);
|
147
|
+
this._trigger("change", event, this._uiHash());
|
148
|
+
}
|
149
|
+
else {
|
150
|
+
this._isAllowed(parentItem, level+childLevels);
|
151
|
+
}
|
152
|
+
|
153
|
+
//Post events to containers
|
154
|
+
this._contactContainers(event);
|
155
|
+
|
156
|
+
//Interconnect with droppables
|
157
|
+
if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
|
158
|
+
|
159
|
+
//Call callbacks
|
160
|
+
this._trigger('sort', event, this._uiHash());
|
161
|
+
|
162
|
+
this.lastPositionAbs = this.positionAbs;
|
163
|
+
return false;
|
164
|
+
|
165
|
+
},
|
166
|
+
|
167
|
+
_mouseStop: function(event, noPropagation) {
|
168
|
+
|
169
|
+
// If the item is in a position not allowed, send it back
|
170
|
+
if (this.beyondMaxLevels) {
|
171
|
+
var parent = this.placeholder.parent().closest(this.options.items);
|
172
|
+
|
173
|
+
for (var i = this.beyondMaxLevels - 1; i > 0; i--) {
|
174
|
+
parent = parent.parent().closest(this.options.items);
|
175
|
+
}
|
176
|
+
|
177
|
+
this.placeholder.removeClass(this.options.errorClass);
|
178
|
+
parent.after(this.placeholder);
|
179
|
+
this._trigger("change", event, this._uiHash());
|
180
|
+
}
|
181
|
+
|
182
|
+
$.ui.sortable.prototype._mouseStop.apply(this, arguments);
|
183
|
+
|
184
|
+
},
|
185
|
+
|
186
|
+
serialize: function(o) {
|
187
|
+
|
188
|
+
var items = this._getItemsAsjQuery(o && o.connected);
|
189
|
+
var str = []; o = o || {};
|
190
|
+
|
191
|
+
$(items).each(function() {
|
192
|
+
var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
|
193
|
+
var pid = ($(o.item || this).parent(o.listType).parent('li').attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
|
194
|
+
if(res) str.push((o.key || res[1]+'['+(o.key && o.expression ? res[1] : res[2])+']')+'='+(pid ? (o.key && o.expression ? pid[1] : pid[2]) : 'root'));
|
195
|
+
});
|
196
|
+
|
197
|
+
if(!str.length && o.key) {
|
198
|
+
str.push(o.key + '=');
|
199
|
+
}
|
200
|
+
|
201
|
+
return str.join('&');
|
202
|
+
|
203
|
+
},
|
204
|
+
|
205
|
+
toHierarchy: function(o) {
|
206
|
+
|
207
|
+
o = o || {};
|
208
|
+
var sDepth = o.startDepthCount || 0;
|
209
|
+
var ret = [];
|
210
|
+
|
211
|
+
$(this.element).children('li').each(function() {
|
212
|
+
var level = _recursiveItems($(this));
|
213
|
+
ret.push(level);
|
214
|
+
});
|
215
|
+
|
216
|
+
return ret;
|
217
|
+
|
218
|
+
function _recursiveItems(li) {
|
219
|
+
var id = ($(li).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
|
220
|
+
if (id != null) {
|
221
|
+
var item = {"id" : id[2]};
|
222
|
+
if ($(li).children(o.listType).children('li').length > 0) {
|
223
|
+
item.children = [];
|
224
|
+
$(li).children(o.listType).children('li').each(function () {
|
225
|
+
var level = _recursiveItems($(this));
|
226
|
+
item.children.push(level);
|
227
|
+
});
|
228
|
+
}
|
229
|
+
return item;
|
230
|
+
}
|
231
|
+
}
|
232
|
+
},
|
233
|
+
|
234
|
+
toArray: function(o) {
|
235
|
+
|
236
|
+
o = o || {};
|
237
|
+
var sDepth = o.startDepthCount || 0;
|
238
|
+
var ret = [];
|
239
|
+
var left = 2;
|
240
|
+
|
241
|
+
ret.push({"item_id": 'root', "parent_id": 'none', "depth": sDepth, "left": '1', "right": ($('li', this.element).length + 1) * 2});
|
242
|
+
|
243
|
+
$(this.element).children('li').each(function () {
|
244
|
+
left = _recursiveArray(this, sDepth + 1, left);
|
245
|
+
});
|
246
|
+
|
247
|
+
function _sortByLeft(a,b) {
|
248
|
+
return a['left'] - b['left'];
|
249
|
+
}
|
250
|
+
ret = ret.sort(_sortByLeft);
|
251
|
+
|
252
|
+
return ret;
|
253
|
+
|
254
|
+
function _recursiveArray(item, depth, left) {
|
255
|
+
|
256
|
+
right = left + 1;
|
257
|
+
|
258
|
+
if ($(item).children(o.listType).children('li').length > 0) {
|
259
|
+
depth ++;
|
260
|
+
$(item).children(o.listType).children('li').each(function () {
|
261
|
+
right = _recursiveArray($(this), depth, right);
|
262
|
+
});
|
263
|
+
depth --;
|
264
|
+
}
|
265
|
+
|
266
|
+
id = ($(item).attr(o.attribute || 'id')).match(o.expression || (/(.+)[-=_](.+)/));
|
267
|
+
|
268
|
+
if (depth === sDepth + 1) pid = 'root';
|
269
|
+
else {
|
270
|
+
parentItem = ($(item).parent(o.listType).parent('li').attr('id')).match(o.expression || (/(.+)[-=_](.+)/));
|
271
|
+
pid = parentItem[2];
|
272
|
+
}
|
273
|
+
|
274
|
+
if (id != null) {
|
275
|
+
ret.push({"item_id": id[2], "parent_id": pid, "depth": depth, "left": left, "right": right});
|
276
|
+
}
|
277
|
+
|
278
|
+
return left = right + 1;
|
279
|
+
}
|
280
|
+
|
281
|
+
},
|
282
|
+
|
283
|
+
_clear: function(event, noPropagation) {
|
284
|
+
|
285
|
+
$.ui.sortable.prototype._clear.apply(this, arguments);
|
286
|
+
|
287
|
+
// Clean last empty ul/ol
|
288
|
+
for (var i = this.items.length - 1; i >= 0; i--) {
|
289
|
+
var item = this.items[i].item[0];
|
290
|
+
this._clearEmpty(item);
|
291
|
+
}
|
292
|
+
return true;
|
293
|
+
|
294
|
+
},
|
295
|
+
|
296
|
+
_clearEmpty: function(item) {
|
297
|
+
|
298
|
+
if (item.children[1] && item.children[1].children.length == 0) {
|
299
|
+
item.removeChild(item.children[1]);
|
300
|
+
}
|
301
|
+
|
302
|
+
},
|
303
|
+
|
304
|
+
_getLevel: function(item) {
|
305
|
+
|
306
|
+
var level = 1;
|
307
|
+
|
308
|
+
if (this.options.listType) {
|
309
|
+
var list = item.closest(this.options.listType);
|
310
|
+
while (!list.is('.ui-sortable')/* && level < this.options.maxLevels*/) {
|
311
|
+
level++;
|
312
|
+
list = list.parent().closest(this.options.listType);
|
313
|
+
}
|
314
|
+
}
|
315
|
+
|
316
|
+
return level;
|
317
|
+
},
|
318
|
+
|
319
|
+
_getChildLevels: function(parent, depth) {
|
320
|
+
var self = this,
|
321
|
+
o = this.options,
|
322
|
+
result = 0;
|
323
|
+
depth = depth || 0;
|
324
|
+
|
325
|
+
$(parent).children(o.listType).children(o.items).each(function (index, child) {
|
326
|
+
result = Math.max(self._getChildLevels(child, depth + 1), result);
|
327
|
+
});
|
328
|
+
|
329
|
+
return depth ? result + 1 : result;
|
330
|
+
},
|
331
|
+
|
332
|
+
_isAllowed: function(parentItem, levels) {
|
333
|
+
var o = this.options
|
334
|
+
// Are we trying to nest under a no-nest or are we nesting too deep?
|
335
|
+
if (parentItem == null || !(parentItem.hasClass(o.disableNesting))) {
|
336
|
+
if (o.maxLevels < levels && o.maxLevels != 0) {
|
337
|
+
this.placeholder.addClass(o.errorClass);
|
338
|
+
this.beyondMaxLevels = levels - o.maxLevels;
|
339
|
+
} else {
|
340
|
+
this.placeholder.removeClass(o.errorClass);
|
341
|
+
this.beyondMaxLevels = 0;
|
342
|
+
}
|
343
|
+
} else {
|
344
|
+
this.placeholder.addClass(o.errorClass);
|
345
|
+
if (o.maxLevels < levels && o.maxLevels != 0) {
|
346
|
+
this.beyondMaxLevels = levels - o.maxLevels;
|
347
|
+
} else {
|
348
|
+
this.beyondMaxLevels = 1;
|
349
|
+
}
|
350
|
+
}
|
351
|
+
}
|
352
|
+
|
353
|
+
}));
|
354
|
+
|
355
|
+
$.ui.nestedSortable.prototype.options = $.extend({}, $.ui.sortable.prototype.options, $.ui.nestedSortable.prototype.options);
|
356
|
+
})(jQuery);
|
@@ -0,0 +1,114 @@
|
|
1
|
+
.nested_set{
|
2
|
+
padding: 0;
|
3
|
+
}
|
4
|
+
.nested_set ol{
|
5
|
+
margin:0 0 0 25px;
|
6
|
+
border-left:1px dashed gray;
|
7
|
+
}
|
8
|
+
.nested_set li{
|
9
|
+
margin-bottom:5px;
|
10
|
+
}
|
11
|
+
.nested_set a{
|
12
|
+
color:#000;
|
13
|
+
font-size:10pt;
|
14
|
+
font-weight:normal;
|
15
|
+
text-decoration:none;
|
16
|
+
line-height: 150%;
|
17
|
+
margin-left:30px;
|
18
|
+
margin-right:120px;
|
19
|
+
display:block;
|
20
|
+
}
|
21
|
+
.nested_set a img{float:left;}
|
22
|
+
|
23
|
+
.nested_set .root a{
|
24
|
+
font-weight:bold;
|
25
|
+
}
|
26
|
+
.nested_set a:hover{
|
27
|
+
color:#2476FF;
|
28
|
+
text-decoration:none;
|
29
|
+
}
|
30
|
+
.nested_set .handle{
|
31
|
+
cursor: move;
|
32
|
+
}
|
33
|
+
.nested_set .link{
|
34
|
+
position: relative;
|
35
|
+
overflow: hidden;
|
36
|
+
zoom:1;
|
37
|
+
padding:5px;
|
38
|
+
margin:0 0 5px 5px;
|
39
|
+
background:#EFEFEF;
|
40
|
+
-moz-border-radius:5px;
|
41
|
+
}
|
42
|
+
.nested_set .link:hover{
|
43
|
+
background:#FEE;
|
44
|
+
}
|
45
|
+
.nested_set .controls{
|
46
|
+
position: absolute;
|
47
|
+
top: 5px; right: 10px;
|
48
|
+
}
|
49
|
+
.nested_set .button{
|
50
|
+
width:20px;
|
51
|
+
height:20px;
|
52
|
+
display:block;
|
53
|
+
float:left;
|
54
|
+
margin:0 0 0 3px;
|
55
|
+
cursor:pointer;
|
56
|
+
}
|
57
|
+
.root_create_image{
|
58
|
+
vertical-align:middle;
|
59
|
+
border:none;
|
60
|
+
}
|
61
|
+
.root_create_link{
|
62
|
+
margin:25px 0;
|
63
|
+
}
|
64
|
+
.root_create_link a{
|
65
|
+
font-size:14pt;
|
66
|
+
}
|
67
|
+
.nested_set .handle{
|
68
|
+
width:16px;
|
69
|
+
height:16px;
|
70
|
+
float:left;
|
71
|
+
background:transparent url(/assets/iconza/blue/move.png) no-repeat scroll center center;
|
72
|
+
}
|
73
|
+
.nested_set .placeholder {
|
74
|
+
background-color:#EEF;
|
75
|
+
border:1px dashed blue;
|
76
|
+
}
|
77
|
+
.nested_set .ui-nestedSortable-error {
|
78
|
+
background:#FAA;
|
79
|
+
color:#8a1f11;
|
80
|
+
}
|
81
|
+
.nested_set .button.new{ background:transparent url(/assets/iconza/blue/add.png) no-repeat scroll center center; }
|
82
|
+
.nested_set .button.new:hover{ background:transparent url(/assets/iconza/red/add.png) no-repeat scroll center center;}
|
83
|
+
|
84
|
+
.nested_set .button.edit{ background:transparent url(/assets/iconza/blue/edit.png) no-repeat scroll center center; }
|
85
|
+
.nested_set .button.edit:hover{ background:transparent url(/assets/iconza/red/edit.png) no-repeat scroll center center;}
|
86
|
+
|
87
|
+
.nested_set .button.up{ background:transparent url(/assets/iconza/blue/up.png) no-repeat scroll center center; }
|
88
|
+
.nested_set .button.up:hover{ background:transparent url(/assets/iconza/red/up.png) no-repeat scroll center center;}
|
89
|
+
|
90
|
+
.nested_set .button.down{ background:transparent url(/assets/iconza/blue/down.png) no-repeat scroll center center; }
|
91
|
+
.nested_set .button.down:hover{ background:transparent url(/assets/iconza/red/down.png) no-repeat scroll center center;}
|
92
|
+
|
93
|
+
.nested_set .button.delete{ background:transparent url(/assets/iconza/blue/delete.png) no-repeat scroll center center; }
|
94
|
+
.nested_set .button.delete:hover{ background:transparent url(/assets/iconza/red/delete.png) no-repeat scroll center center;}
|
95
|
+
|
96
|
+
.nested_set .button.hard_delete{ background:transparent url(/assets/iconza/gray/delete.png) no-repeat scroll center center; }
|
97
|
+
.nested_set .button.hard_delete:hover{ background:transparent url(/assets/iconza/red/delete.png) no-repeat scroll center center;}
|
98
|
+
|
99
|
+
.nested_set .undeleted{ background:transparent url(/assets/iconza/gray/delete.png) no-repeat scroll center center; }
|
100
|
+
.nested_set .cantup{ background:transparent url(/assets/iconza/gray/up.png) no-repeat scroll center center; }
|
101
|
+
.nested_set .cantdown{ background:transparent url(/assets/iconza/gray/down.png) no-repeat scroll center center; }
|
102
|
+
|
103
|
+
/*STATES*/
|
104
|
+
.nested_set .unsafe{ background:transparent url(/assets/modern/mini-icons/unsafe.png) no-repeat scroll center center; cursor: help; }
|
105
|
+
.nested_set .draft{ background:transparent url(/assets/modern/mini-icons/draft.png) no-repeat scroll center center; cursor: help;}
|
106
|
+
.nested_set .published{ background:transparent url(/assets/modern/mini-icons/publiched.png) no-repeat scroll center center; cursor: help;}
|
107
|
+
.nested_set .restricted{ background:transparent url(/assets/modern/mini-icons/restricted.png) no-repeat scroll center center; cursor: help;}
|
108
|
+
.nested_set .archived{ background:transparent url(/assets/modern/mini-icons/archived.png) no-repeat scroll center center; cursor: help;}
|
109
|
+
.nested_set .deleted{ background:transparent url(/assets/modern/mini-icons/bin.png) no-repeat scroll center center; cursor: help;}
|
110
|
+
|
111
|
+
/* Moderation states*/
|
112
|
+
.nested_set .moderation_unsafe{ background:transparent url(/assets/modern/mini-icons/moderation_unsafe.png) no-repeat scroll center center; cursor: help;}
|
113
|
+
.nested_set .moderation_safe{ background:transparent url(/assets/modern/mini-icons/shield.png) no-repeat scroll center center; cursor: help;}
|
114
|
+
.nested_set .moderation_blocked{ background:transparent url(/assets/modern/mini-icons/moderation_blocked.png) no-repeat scroll center center; cursor: help;}
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module TheSortableTreeController
|
2
|
+
# include TheSortableTreeController::ReversedRebuild
|
3
|
+
# include TheSortableTreeController::Rebuild
|
4
|
+
|
5
|
+
module DefineVariablesMethod
|
6
|
+
public
|
7
|
+
def the_define_common_variables
|
8
|
+
collection = self.class.to_s.underscore.split('_').first # recipes
|
9
|
+
variable = collection.singularize # recipe
|
10
|
+
klass = variable.classify.constantize # Recipe
|
11
|
+
["@#{variable}", collection, klass]
|
12
|
+
end
|
13
|
+
end#DefineVariablesMethod
|
14
|
+
|
15
|
+
module Rebuild
|
16
|
+
include DefineVariablesMethod
|
17
|
+
public
|
18
|
+
def rebuild
|
19
|
+
id = params[:id].to_i
|
20
|
+
parent_id = params[:parent_id].to_i
|
21
|
+
prev_id = params[:prev_id].to_i
|
22
|
+
next_id = params[:next_id].to_i
|
23
|
+
|
24
|
+
render :text => "Do nothing" and return if parent_id.zero? && prev_id.zero? && next_id.zero?
|
25
|
+
|
26
|
+
variable, collection, klass = self.the_define_common_variables
|
27
|
+
variable = self.instance_variable_set(variable, klass.find(id))
|
28
|
+
|
29
|
+
if prev_id.zero? && next_id.zero?
|
30
|
+
variable.move_to_child_of klass.find(parent_id)
|
31
|
+
elsif !prev_id.zero?
|
32
|
+
variable.move_to_right_of klass.find(prev_id)
|
33
|
+
elsif !next_id.zero?
|
34
|
+
variable.move_to_left_of klass.find(next_id)
|
35
|
+
end
|
36
|
+
|
37
|
+
render(:nothing => true)
|
38
|
+
end
|
39
|
+
end#Rebuild
|
40
|
+
|
41
|
+
module ReversedRebuild
|
42
|
+
include DefineVariablesMethod
|
43
|
+
public
|
44
|
+
def rebuild
|
45
|
+
id = params[:id].to_i
|
46
|
+
parent_id = params[:parent_id].to_i
|
47
|
+
prev_id = params[:prev_id].to_i
|
48
|
+
next_id = params[:next_id].to_i
|
49
|
+
|
50
|
+
render :text => "Do nothing" and return if parent_id.zero? && prev_id.zero? && next_id.zero?
|
51
|
+
|
52
|
+
variable, collection, klass = self.the_define_common_variables
|
53
|
+
variable = self.instance_variable_set(variable, klass.find(id))
|
54
|
+
|
55
|
+
if prev_id.zero? && next_id.zero?
|
56
|
+
variable.move_to_child_of klass.find(parent_id)
|
57
|
+
elsif !prev_id.zero?
|
58
|
+
variable.move_to_left_of klass.find(prev_id)
|
59
|
+
elsif !next_id.zero?
|
60
|
+
variable.move_to_right_of klass.find(next_id)
|
61
|
+
end
|
62
|
+
|
63
|
+
render(:nothing => true)
|
64
|
+
end
|
65
|
+
end#ReversedRebuild
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module TheSortableTreeHelper
|
2
|
+
# Publicated by MIT
|
3
|
+
# Nested Set View Helper
|
4
|
+
# Ilya Zykin, zykin-ilya@ya.ru, Russia, Ivanovo 2009-2011
|
5
|
+
# github.com/the-teacher
|
6
|
+
#-------------------------------------------------------------------------------------------------------
|
7
|
+
|
8
|
+
# = sortable_tree(@pages)
|
9
|
+
# = sortable_tree @products, :klass => :product, :path => 'products/the_sortable_tree', :rebuild_url => rebuild_products_path
|
10
|
+
|
11
|
+
def create_root_element_link(options= {})
|
12
|
+
opts= {:top_root => false}.merge!(options)
|
13
|
+
render :partial => "#{opts[:path]}/new", :locals => { :opts => opts }
|
14
|
+
end
|
15
|
+
|
16
|
+
def ans_controls(node, options= {})
|
17
|
+
opts= {
|
18
|
+
:id_field => 'id', # id field name, id by default
|
19
|
+
:first => false, # first element flag
|
20
|
+
:last => false, # last element flag
|
21
|
+
:has_childs => false # has childs?
|
22
|
+
}.merge!(options)
|
23
|
+
render :partial => "#{opts[:path]}/controls", :locals => { :node => node, :opts => opts }
|
24
|
+
end
|
25
|
+
|
26
|
+
def sortable_tree(tree, options= {})
|
27
|
+
path = 'the_sortable_tree'
|
28
|
+
path = options[:path] if options[:path] && File.directory?([Rails.root, :app, :views, options[:path]].join '/')
|
29
|
+
klass = options[:klass].to_s
|
30
|
+
tree = sortable_tree_bilder(tree, options.merge!({:path => path, :klass => klass}))
|
31
|
+
render :partial => "#{path}/tree", :locals => { :tree => tree, :opts => options }
|
32
|
+
end
|
33
|
+
|
34
|
+
def sortable_tree_bilder(tree, options= {})
|
35
|
+
result= ''
|
36
|
+
opts= {
|
37
|
+
:node => nil, # node
|
38
|
+
:admin => true, # render admin tree?
|
39
|
+
:root => false, # is it root node?
|
40
|
+
:id_field => 'id', # id field name, id by default
|
41
|
+
:first => false, # first element flag
|
42
|
+
:last => false, # last element flag
|
43
|
+
:level => 0, # recursion level
|
44
|
+
:clean => true # delete element from tree after rendering. ~25% time economy when rendering tree once on a page
|
45
|
+
}.merge!(options)
|
46
|
+
|
47
|
+
node = opts[:node]
|
48
|
+
root = opts[:root]
|
49
|
+
|
50
|
+
# must be string
|
51
|
+
opts[:id_field] = opts[:id_field].to_s
|
52
|
+
|
53
|
+
unless node
|
54
|
+
roots= tree.select{|elem| elem.parent_id.nil?}
|
55
|
+
# find ids of first and last root node
|
56
|
+
roots_first_id= roots.empty? ? nil : roots.first.id
|
57
|
+
roots_last_id= roots.empty? ? nil : roots.last.id
|
58
|
+
|
59
|
+
# render roots
|
60
|
+
roots.each do |root|
|
61
|
+
is_first= (root.id==roots_first_id)
|
62
|
+
is_last= (root.id==roots_last_id)
|
63
|
+
_opts = opts.merge({:node => root, :root => true, :level => opts[:level].next, :first => is_first, :last => is_last})
|
64
|
+
result<< sortable_tree_bilder(tree, _opts)
|
65
|
+
end
|
66
|
+
else
|
67
|
+
res= ''
|
68
|
+
controls= ''
|
69
|
+
childs_res= ''
|
70
|
+
|
71
|
+
# select childs
|
72
|
+
childs= tree.select{|elem| elem.parent_id == node.id}
|
73
|
+
opts.merge!({:has_childs => childs.blank?})
|
74
|
+
|
75
|
+
# admin controls
|
76
|
+
if opts[:admin]
|
77
|
+
c = ans_controls(node, opts)
|
78
|
+
controls= content_tag(:span, c, :class => :controls)
|
79
|
+
end
|
80
|
+
|
81
|
+
# find id of first and last node
|
82
|
+
childs_first_id= childs.empty? ? nil : childs.first.id
|
83
|
+
childs_last_id= childs.empty? ? nil : childs.last.id
|
84
|
+
|
85
|
+
# render childs
|
86
|
+
childs.each do |elem|
|
87
|
+
is_first= (elem.id==childs_first_id)
|
88
|
+
is_last= (elem.id==childs_last_id)
|
89
|
+
_opts = opts.merge({:node => elem, :root => false, :level => opts[:level].next, :first => is_first, :last => is_last})
|
90
|
+
childs_res << sortable_tree_bilder(tree, _opts)
|
91
|
+
end
|
92
|
+
|
93
|
+
# build views
|
94
|
+
childs_res= childs_res.blank? ? '' : render(:partial => "#{opts[:path]}/nested_set", :locals => {:opts => opts, :parent => node, :childs => childs_res})
|
95
|
+
link= render(:partial => "#{opts[:path]}/link", :locals => {:opts => opts, :node => node, :root => root, :controls => controls})
|
96
|
+
res= render(:partial => "#{opts[:path]}/nested_set_item", :locals => {:opts => opts, :node => node, :link => link, :childs => childs_res})
|
97
|
+
|
98
|
+
# delete current node from tree if you want
|
99
|
+
# recursively moving by tree is 25%+ faster on 500 elems
|
100
|
+
# remove from Array, not from DB! ;) ?! Array#pull?!
|
101
|
+
# tree.delete(node) if opts[:clean]
|
102
|
+
|
103
|
+
result << res
|
104
|
+
end
|
105
|
+
raw result
|
106
|
+
end#sortable_tree_bilder
|
107
|
+
end# module
|
@@ -0,0 +1,16 @@
|
|
1
|
+
-# EDIT
|
2
|
+
- edit= link_to "", polymorphic_url(node, :action => :edit), :title => t("#{opts[:klass].pluralize}.edit_this"), :class => "button edit"
|
3
|
+
-# DELETE
|
4
|
+
- if opts[:has_childs]
|
5
|
+
- delete= link_to("", url_for(node),
|
6
|
+
:title => t("#{opts[:klass].pluralize}.delete"),
|
7
|
+
:method => :delete,
|
8
|
+
:confirm => t("#{opts[:klass].pluralize}.delete_confirm"),
|
9
|
+
:class => "button delete")
|
10
|
+
- else
|
11
|
+
- delete= link_to("", "#",
|
12
|
+
:title => t("#{opts[:klass].pluralize}.cant_be_deleted"),
|
13
|
+
:onclick => t("#{opts[:klass].pluralize}.delete_nested_elements"),
|
14
|
+
:class => "button undeleted")
|
15
|
+
-# OUTPUT
|
16
|
+
= edit + delete
|
@@ -0,0 +1,17 @@
|
|
1
|
+
:javascript
|
2
|
+
$(document).ready(function(){
|
3
|
+
$('ol.sortable').nestedSortable({
|
4
|
+
disableNesting: 'no-nest',
|
5
|
+
forcePlaceholderSize: true,
|
6
|
+
handle: 'div.handle',
|
7
|
+
helper: 'clone',
|
8
|
+
items: 'li',
|
9
|
+
maxLevels: 3,
|
10
|
+
opacity: .6,
|
11
|
+
placeholder: 'placeholder',
|
12
|
+
revert: 250,
|
13
|
+
tabSize: 25,
|
14
|
+
tolerance: 'pointer',
|
15
|
+
toleranceElement: '> div'
|
16
|
+
})
|
17
|
+
});
|
@@ -0,0 +1,12 @@
|
|
1
|
+
:javascript
|
2
|
+
$(document).ready(function(){
|
3
|
+
$('ol.sortable').sortable({
|
4
|
+
update: function(event, ui){
|
5
|
+
parent_id = ui.item.parent().parent().attr('id');
|
6
|
+
item_id = ui.item.attr('id');
|
7
|
+
prev_id = ui.item.prev().attr('id');
|
8
|
+
next_id = ui.item.next().attr('id');
|
9
|
+
sortable_tree(item_id, parent_id, prev_id, next_id);
|
10
|
+
}
|
11
|
+
});
|
12
|
+
});
|
@@ -0,0 +1,21 @@
|
|
1
|
+
:javascript
|
2
|
+
function sortable_tree(item_id, parent_id, prev_id, next_id){
|
3
|
+
jQuery.ajax({
|
4
|
+
type: 'POST',
|
5
|
+
url: '#{opts[:rebuild_url]}',
|
6
|
+
data: { id: item_id,
|
7
|
+
parent_id: parent_id,
|
8
|
+
prev_id: prev_id,
|
9
|
+
next_id: next_id },
|
10
|
+
dataType: 'script',
|
11
|
+
beforeSend: function(xhr){
|
12
|
+
//$('##{opts[:klass]}_nested_set .handle').hide();
|
13
|
+
},
|
14
|
+
success: function(data, status, xhr){
|
15
|
+
//$('##{opts[:klass]}_nested_set .handle').show();
|
16
|
+
},
|
17
|
+
error: function(xhr, status, error){
|
18
|
+
alert(error);
|
19
|
+
}
|
20
|
+
});
|
21
|
+
}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
- if opts[:admin]
|
2
|
+
- handle= content_tag :div, raw(" "), :class => :handle
|
3
|
+
- link= link_to(node.title, url_for(node), :title => node.title)
|
4
|
+
- content= raw(handle.to_s) + controls + link
|
5
|
+
%div{ :class => "link#{' root' if root}" }
|
6
|
+
= content
|
7
|
+
- else
|
8
|
+
%div{:class => "link #{"root" if root}"}
|
9
|
+
= link_to(node.title, polymorphic_path(node), :title => node.title)
|
10
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
= content_tag(:ol, raw(childs), :id=>"#{parent.id}_page_childs")
|
@@ -0,0 +1,6 @@
|
|
1
|
+
= render :partial => "#{opts[:path]}/js_init_sortable_tree"
|
2
|
+
= render :partial => "#{opts[:path]}/js_on_update_tree"
|
3
|
+
= render :partial => "#{opts[:path]}/js_rebuild_ajax", :locals => { :opts => opts }
|
4
|
+
%ol.sortable.ui-sortable.nested_set{ :id => "#{opts[:klass]}_nested_set" }
|
5
|
+
= raw tree
|
6
|
+
= render :partial => "#{opts[:path]}/new", :locals => { :opts => opts }
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module TheSortableTree
|
2
|
+
module Generators
|
3
|
+
class ViewsGenerator < Rails::Generators::NamedBase
|
4
|
+
source_root File.expand_path('../../../../app/views', __FILE__)
|
5
|
+
|
6
|
+
def self.banner
|
7
|
+
<<-BANNER.chomp
|
8
|
+
rails g the_sortable_tree:views MODEL
|
9
|
+
Copies files for rendering sortable nested set
|
10
|
+
BANNER
|
11
|
+
end
|
12
|
+
|
13
|
+
def copy_sortable_tree_files
|
14
|
+
directory "the_sortable_tree", "app/views/#{folder}/the_sortable_tree"
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def folder
|
20
|
+
name.pluralize.downcase
|
21
|
+
end
|
22
|
+
|
23
|
+
end#ViewsGenerator
|
24
|
+
end
|
25
|
+
end
|
data/the_sortable_tree.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.authors = ["Ilya N. Zykin"]
|
9
9
|
s.email = ["zykin-ilya@ya.ru"]
|
10
10
|
s.homepage = "https://github.com/the-teacher/the_sortable_tree"
|
11
|
-
s.summary = %q{
|
12
|
-
s.description = %q{
|
11
|
+
s.summary = %q{Drug&Drop GUI for nested_set gem. Sortable tree view helper}
|
12
|
+
s.description = %q{Drug&Drop GUI for nested_set gem. Sortable tree view helper}
|
13
13
|
|
14
14
|
s.rubyforge_project = "the_sortable_tree"
|
15
15
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: the_sortable_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
12
|
+
date: 2011-12-02 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: haml
|
16
|
-
requirement: &
|
16
|
+
requirement: &84037710 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,8 +21,8 @@ dependencies:
|
|
21
21
|
version: '3.0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
25
|
-
description:
|
24
|
+
version_requirements: *84037710
|
25
|
+
description: Drug&Drop GUI for nested_set gem. Sortable tree view helper
|
26
26
|
email:
|
27
27
|
- zykin-ilya@ya.ru
|
28
28
|
executables: []
|
@@ -31,8 +31,47 @@ extra_rdoc_files: []
|
|
31
31
|
files:
|
32
32
|
- .gitignore
|
33
33
|
- Gemfile
|
34
|
+
- README.md
|
34
35
|
- Rakefile
|
36
|
+
- app/assets/images/iconza/blue/add.png
|
37
|
+
- app/assets/images/iconza/blue/delete.png
|
38
|
+
- app/assets/images/iconza/blue/down.png
|
39
|
+
- app/assets/images/iconza/blue/downloads_folder.png
|
40
|
+
- app/assets/images/iconza/blue/edit.png
|
41
|
+
- app/assets/images/iconza/blue/move.png
|
42
|
+
- app/assets/images/iconza/blue/up.png
|
43
|
+
- app/assets/images/iconza/gray/add.png
|
44
|
+
- app/assets/images/iconza/gray/delete.png
|
45
|
+
- app/assets/images/iconza/gray/down.png
|
46
|
+
- app/assets/images/iconza/gray/edit.png
|
47
|
+
- app/assets/images/iconza/gray/lock.png
|
48
|
+
- app/assets/images/iconza/gray/mail.png
|
49
|
+
- app/assets/images/iconza/gray/push_pin.png
|
50
|
+
- app/assets/images/iconza/gray/up.png
|
51
|
+
- app/assets/images/iconza/red/add.png
|
52
|
+
- app/assets/images/iconza/red/delete.png
|
53
|
+
- app/assets/images/iconza/red/down.png
|
54
|
+
- app/assets/images/iconza/red/edit.png
|
55
|
+
- app/assets/images/iconza/red/newspaper.png
|
56
|
+
- app/assets/images/iconza/red/trash.png
|
57
|
+
- app/assets/images/iconza/red/up.png
|
58
|
+
- app/assets/images/iconza/red/zoom.png
|
59
|
+
- app/assets/javascripts/jquery.ui.nestedSortable.js
|
60
|
+
- app/assets/stylesheets/the_sortable_tree.css
|
61
|
+
- app/controllers/the_sortable_tree_controller.rb
|
62
|
+
- app/helpers/the_sortable_tree_helper.rb
|
63
|
+
- app/views/the_sortable_tree/_controls.html.haml
|
64
|
+
- app/views/the_sortable_tree/_js_init_sortable_tree.html.haml
|
65
|
+
- app/views/the_sortable_tree/_js_on_update_tree.html.haml
|
66
|
+
- app/views/the_sortable_tree/_js_rebuild_ajax.html.haml
|
67
|
+
- app/views/the_sortable_tree/_link.html.haml
|
68
|
+
- app/views/the_sortable_tree/_nested_set.html.haml
|
69
|
+
- app/views/the_sortable_tree/_nested_set_item.html.haml
|
70
|
+
- app/views/the_sortable_tree/_new.html.haml
|
71
|
+
- app/views/the_sortable_tree/_tree.html.haml
|
72
|
+
- lib/generators/the_sortable_tree/views_generator.rb
|
35
73
|
- lib/the_sortable_tree.rb
|
74
|
+
- lib/the_sortable_tree/engine.rb
|
36
75
|
- lib/the_sortable_tree/version.rb
|
37
76
|
- the_sortable_tree.gemspec
|
38
77
|
homepage: https://github.com/the-teacher/the_sortable_tree
|
@@ -58,5 +97,5 @@ rubyforge_project: the_sortable_tree
|
|
58
97
|
rubygems_version: 1.8.10
|
59
98
|
signing_key:
|
60
99
|
specification_version: 3
|
61
|
-
summary:
|
100
|
+
summary: Drug&Drop GUI for nested_set gem. Sortable tree view helper
|
62
101
|
test_files: []
|