the_sortable_tree 0.0.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|