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.
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: []