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.
Files changed (42) hide show
  1. data/README.md +67 -0
  2. data/app/assets/images/iconza/blue/add.png +0 -0
  3. data/app/assets/images/iconza/blue/delete.png +0 -0
  4. data/app/assets/images/iconza/blue/down.png +0 -0
  5. data/app/assets/images/iconza/blue/downloads_folder.png +0 -0
  6. data/app/assets/images/iconza/blue/edit.png +0 -0
  7. data/app/assets/images/iconza/blue/move.png +0 -0
  8. data/app/assets/images/iconza/blue/up.png +0 -0
  9. data/app/assets/images/iconza/gray/add.png +0 -0
  10. data/app/assets/images/iconza/gray/delete.png +0 -0
  11. data/app/assets/images/iconza/gray/down.png +0 -0
  12. data/app/assets/images/iconza/gray/edit.png +0 -0
  13. data/app/assets/images/iconza/gray/lock.png +0 -0
  14. data/app/assets/images/iconza/gray/mail.png +0 -0
  15. data/app/assets/images/iconza/gray/push_pin.png +0 -0
  16. data/app/assets/images/iconza/gray/up.png +0 -0
  17. data/app/assets/images/iconza/red/add.png +0 -0
  18. data/app/assets/images/iconza/red/delete.png +0 -0
  19. data/app/assets/images/iconza/red/down.png +0 -0
  20. data/app/assets/images/iconza/red/edit.png +0 -0
  21. data/app/assets/images/iconza/red/newspaper.png +0 -0
  22. data/app/assets/images/iconza/red/trash.png +0 -0
  23. data/app/assets/images/iconza/red/up.png +0 -0
  24. data/app/assets/images/iconza/red/zoom.png +0 -0
  25. data/app/assets/javascripts/jquery.ui.nestedSortable.js +356 -0
  26. data/app/assets/stylesheets/the_sortable_tree.css +114 -0
  27. data/app/controllers/the_sortable_tree_controller.rb +67 -0
  28. data/app/helpers/the_sortable_tree_helper.rb +107 -0
  29. data/app/views/the_sortable_tree/_controls.html.haml +16 -0
  30. data/app/views/the_sortable_tree/_js_init_sortable_tree.html.haml +17 -0
  31. data/app/views/the_sortable_tree/_js_on_update_tree.html.haml +12 -0
  32. data/app/views/the_sortable_tree/_js_rebuild_ajax.html.haml +21 -0
  33. data/app/views/the_sortable_tree/_link.html.haml +10 -0
  34. data/app/views/the_sortable_tree/_nested_set.html.haml +1 -0
  35. data/app/views/the_sortable_tree/_nested_set_item.html.haml +3 -0
  36. data/app/views/the_sortable_tree/_new.html.haml +3 -0
  37. data/app/views/the_sortable_tree/_tree.html.haml +6 -0
  38. data/lib/generators/the_sortable_tree/views_generator.rb +25 -0
  39. data/lib/the_sortable_tree/engine.rb +10 -0
  40. data/lib/the_sortable_tree/version.rb +1 -1
  41. data/the_sortable_tree.gemspec +2 -2
  42. 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
@@ -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("&nbsp;"), :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,3 @@
1
+ %li{ :id => "#{node.id}_#{opts[:klass]}" }
2
+ = raw link
3
+ = childs
@@ -0,0 +1,3 @@
1
+ - image= image_tag('iconza/blue/add.png', :class => :root_create_image) + raw('&nbsp;')
2
+ - link= link_to(image + raw(t("#{opts[:klass].pluralize}.new.create")), url_for(:controller => opts[:klass].pluralize, :action => :new))
3
+ .root_create_link= link
@@ -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
@@ -0,0 +1,10 @@
1
+ require 'the_sortable_tree'
2
+ require 'rails'
3
+
4
+ module TheSortableTree
5
+ class Engine < Rails::Engine
6
+ #config.to_prepare do
7
+ # ApplicationController.send :helper, TheSortableTreeHelper
8
+ #end
9
+ end#Engine
10
+ end#TheRole
@@ -1,3 +1,3 @@
1
1
  module TheSortableTree
2
- VERSION = "0.0.1"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -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{Sortable GUI for nested_set gem! Rails 3.1+}
12
- s.description = %q{Sortable GUI for nested_set gem! Rails 3.1+}
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.1
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-11-23 00:00:00.000000000Z
12
+ date: 2011-12-02 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: haml
16
- requirement: &70173150 !ruby/object:Gem::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: *70173150
25
- description: Sortable GUI for nested_set gem! Rails 3.1+
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: Sortable GUI for nested_set gem! Rails 3.1+
100
+ summary: Drug&Drop GUI for nested_set gem. Sortable tree view helper
62
101
  test_files: []