rails_admin_nested_set 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+
19
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in nested_set_ui.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Gleb Tv
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # NestedSetUi
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'rails_admin_nested_set'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install rails_admin_nested_set
18
+
19
+ ## Usage with rails_admin
20
+
21
+ Add to /app/assets/javascripts/rails_admin/custom/ui.coffee
22
+
23
+ #= require rails_admin_nested_set
24
+
25
+ Add to /app/assets/stylesheets/rails_admin/custom/theming.sass
26
+
27
+ @import rails_admin_nested_set.css.scss
28
+
29
+ Add the nested_set action for each model or only for models you need
30
+
31
+ RailsAdmin.config do |config|
32
+ config.actions do
33
+ ......
34
+ nested_set do
35
+ visible do
36
+ %w(Page).include? bindings[:abstract_model].model_name
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ In model config:
43
+
44
+ rails_admin do
45
+ ...
46
+
47
+ nested_set({
48
+ max_depth: 1
49
+ })
50
+ end
51
+
52
+ ## Contributing
53
+
54
+ 1. Fork it
55
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
56
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
57
+ 4. Push to the branch (`git push origin my-new-feature`)
58
+ 5. Create new Pull Request
59
+
60
+ ## Credits
61
+
62
+ Some ideas and code for this gem are taken from:
63
+
64
+ https://github.com/dalpo/rails_admin_nestable (MIT license)
65
+ https://github.com/the-teacher/the_sortable_tree (MIT license)
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,429 @@
1
+ /*
2
+ * jQuery UI Nested Sortable
3
+ * v 1.3.5 / 21 jun 2012
4
+ * http://mjsarfatti.com/code/nestedSortable
5
+ *
6
+ * Depends on:
7
+ * jquery.ui.sortable.js 1.8+
8
+ *
9
+ * Copyright (c) 2010-2012 Manuele J Sarfatti
10
+ * Licensed under the MIT License
11
+ * http://www.opensource.org/licenses/mit-license.php
12
+ */
13
+
14
+ (function($) {
15
+
16
+ $.widget("mjs.nestedSortable", $.extend({}, $.ui.sortable.prototype, {
17
+
18
+ options: {
19
+ tabSize: 20,
20
+ disableNesting: 'mjs-nestedSortable-no-nesting',
21
+ errorClass: 'mjs-nestedSortable-error',
22
+ doNotClear: false,
23
+ listType: 'ol',
24
+ maxLevels: 0,
25
+ protectRoot: false,
26
+ rootID: null,
27
+ rtl: false,
28
+ isAllowed: function(item, parent) { return true; }
29
+ },
30
+
31
+ _create: function() {
32
+ this.element.data('sortable', this.element.data('nestedSortable'));
33
+
34
+ if (!this.element.is(this.options.listType))
35
+ throw new Error('nestedSortable: Please check the listType option is set to your actual list type');
36
+
37
+ return $.ui.sortable.prototype._create.apply(this, arguments);
38
+ },
39
+
40
+ destroy: function() {
41
+ this.element
42
+ .removeData("nestedSortable")
43
+ .unbind(".nestedSortable");
44
+ return $.ui.sortable.prototype.destroy.apply(this, arguments);
45
+ },
46
+
47
+ _mouseDrag: function(event) {
48
+
49
+ //Compute the helpers position
50
+ this.position = this._generatePosition(event);
51
+ this.positionAbs = this._convertPositionTo("absolute");
52
+
53
+ if (!this.lastPositionAbs) {
54
+ this.lastPositionAbs = this.positionAbs;
55
+ }
56
+
57
+ var o = this.options;
58
+
59
+ //Do scrolling
60
+ if(this.options.scroll) {
61
+ var scrolled = false;
62
+ if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
63
+
64
+ if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
65
+ this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
66
+ else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
67
+ this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
68
+
69
+ if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
70
+ this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
71
+ else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
72
+ this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
73
+
74
+ } else {
75
+
76
+ if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
77
+ scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
78
+ else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
79
+ scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
80
+
81
+ if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
82
+ scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
83
+ else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
84
+ scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
85
+
86
+ }
87
+
88
+ if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
89
+ $.ui.ddmanager.prepareOffsets(this, event);
90
+ }
91
+
92
+ //Regenerate the absolute position used for position checks
93
+ this.positionAbs = this._convertPositionTo("absolute");
94
+
95
+ // Find the top offset before rearrangement,
96
+ var previousTopOffset = this.placeholder.offset().top;
97
+
98
+ //Set the helper position
99
+ if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
100
+ if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
101
+
102
+ //Rearrange
103
+ for (var i = this.items.length - 1; i >= 0; i--) {
104
+
105
+ //Cache variables and intersection, continue if no intersection
106
+ var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
107
+ if (!intersection) continue;
108
+
109
+ if(itemElement != this.currentItem[0] //cannot intersect with itself
110
+ && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
111
+ && !$.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
112
+ && (this.options.type == 'semi-dynamic' ? !$.contains(this.element[0], itemElement) : true)
113
+ //&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
114
+ ) {
115
+
116
+ $(itemElement).mouseenter();
117
+
118
+ this.direction = intersection == 1 ? "down" : "up";
119
+
120
+ if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
121
+ $(itemElement).mouseleave();
122
+ this._rearrange(event, item);
123
+ } else {
124
+ break;
125
+ }
126
+
127
+ // Clear emtpy ul's/ol's
128
+ this._clearEmpty(itemElement);
129
+
130
+ this._trigger("change", event, this._uiHash());
131
+ break;
132
+ }
133
+ }
134
+
135
+ var parentItem = (this.placeholder[0].parentNode.parentNode &&
136
+ $(this.placeholder[0].parentNode.parentNode).closest('.ui-sortable').length)
137
+ ? $(this.placeholder[0].parentNode.parentNode)
138
+ : null,
139
+ level = this._getLevel(this.placeholder),
140
+ childLevels = this._getChildLevels(this.helper);
141
+
142
+ // To find the previous sibling in the list, keep backtracking until we hit a valid list item.
143
+ var previousItem = this.placeholder[0].previousSibling ? $(this.placeholder[0].previousSibling) : null;
144
+ if (previousItem != null) {
145
+ while (previousItem[0].nodeName.toLowerCase() != 'li' || previousItem[0] == this.currentItem[0] || previousItem[0] == this.helper[0]) {
146
+ if (previousItem[0].previousSibling) {
147
+ previousItem = $(previousItem[0].previousSibling);
148
+ } else {
149
+ previousItem = null;
150
+ break;
151
+ }
152
+ }
153
+ }
154
+
155
+ // To find the next sibling in the list, keep stepping forward until we hit a valid list item.
156
+ var nextItem = this.placeholder[0].nextSibling ? $(this.placeholder[0].nextSibling) : null;
157
+ if (nextItem != null) {
158
+ while (nextItem[0].nodeName.toLowerCase() != 'li' || nextItem[0] == this.currentItem[0] || nextItem[0] == this.helper[0]) {
159
+ if (nextItem[0].nextSibling) {
160
+ nextItem = $(nextItem[0].nextSibling);
161
+ } else {
162
+ nextItem = null;
163
+ break;
164
+ }
165
+ }
166
+ }
167
+
168
+ var newList = document.createElement(o.listType);
169
+
170
+ this.beyondMaxLevels = 0;
171
+
172
+ // If the item is moved to the left, send it to its parent's level unless there are siblings below it.
173
+ if (parentItem != null && nextItem == null &&
174
+ (o.rtl && (this.positionAbs.left + this.helper.outerWidth() > parentItem.offset().left + parentItem.outerWidth()) ||
175
+ !o.rtl && (this.positionAbs.left < parentItem.offset().left))) {
176
+ parentItem.after(this.placeholder[0]);
177
+ this._clearEmpty(parentItem[0]);
178
+ this._trigger("change", event, this._uiHash());
179
+ }
180
+ // If the item is below a sibling and is moved to the right, make it a child of that sibling.
181
+ else if (previousItem != null &&
182
+ (o.rtl && (this.positionAbs.left + this.helper.outerWidth() < previousItem.offset().left + previousItem.outerWidth() - o.tabSize) ||
183
+ !o.rtl && (this.positionAbs.left > previousItem.offset().left + o.tabSize))) {
184
+ this._isAllowed(previousItem, level, level+childLevels+1);
185
+ if (!previousItem.children(o.listType).length) {
186
+ previousItem[0].appendChild(newList);
187
+ }
188
+ // If this item is being moved from the top, add it to the top of the list.
189
+ if (previousTopOffset && (previousTopOffset <= previousItem.offset().top)) {
190
+ previousItem.children(o.listType).prepend(this.placeholder);
191
+ }
192
+ // Otherwise, add it to the bottom of the list.
193
+ else {
194
+ previousItem.children(o.listType)[0].appendChild(this.placeholder[0]);
195
+ }
196
+ this._trigger("change", event, this._uiHash());
197
+ }
198
+ else {
199
+ this._isAllowed(parentItem, level, level+childLevels);
200
+ }
201
+
202
+ //Post events to containers
203
+ this._contactContainers(event);
204
+
205
+ //Interconnect with droppables
206
+ if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
207
+
208
+ //Call callbacks
209
+ this._trigger('sort', event, this._uiHash());
210
+
211
+ this.lastPositionAbs = this.positionAbs;
212
+ return false;
213
+
214
+ },
215
+
216
+ _mouseStop: function(event, noPropagation) {
217
+
218
+ // If the item is in a position not allowed, send it back
219
+ if (this.beyondMaxLevels) {
220
+
221
+ this.placeholder.removeClass(this.options.errorClass);
222
+
223
+ if (this.domPosition.prev) {
224
+ $(this.domPosition.prev).after(this.placeholder);
225
+ } else {
226
+ $(this.domPosition.parent).prepend(this.placeholder);
227
+ }
228
+
229
+ this._trigger("revert", event, this._uiHash());
230
+
231
+ }
232
+
233
+ // Clean last empty ul/ol
234
+ for (var i = this.items.length - 1; i >= 0; i--) {
235
+ var item = this.items[i].item[0];
236
+ this._clearEmpty(item);
237
+ }
238
+
239
+ $.ui.sortable.prototype._mouseStop.apply(this, arguments);
240
+
241
+ },
242
+
243
+ serialize: function(options) {
244
+
245
+ var o = $.extend({}, this.options, options),
246
+ items = this._getItemsAsjQuery(o && o.connected),
247
+ str = [];
248
+
249
+ $(items).each(function() {
250
+ var res = ($(o.item || this).attr(o.attribute || 'id') || '')
251
+ .match(o.expression || (/(.+)[-=_](.+)/)),
252
+ pid = ($(o.item || this).parent(o.listType)
253
+ .parent(o.items)
254
+ .attr(o.attribute || 'id') || '')
255
+ .match(o.expression || (/(.+)[-=_](.+)/));
256
+
257
+ if (res) {
258
+ str.push(((o.key || res[1]) + '[' + (o.key && o.expression ? res[1] : res[2]) + ']')
259
+ + '='
260
+ + (pid ? (o.key && o.expression ? pid[1] : pid[2]) : o.rootID));
261
+ }
262
+ });
263
+
264
+ if(!str.length && o.key) {
265
+ str.push(o.key + '=');
266
+ }
267
+
268
+ return str.join('&');
269
+
270
+ },
271
+
272
+ toHierarchy: function(options) {
273
+
274
+ var o = $.extend({}, this.options, options),
275
+ sDepth = o.startDepthCount || 0,
276
+ ret = [];
277
+
278
+ $(this.element).children(o.items).each(function () {
279
+ var level = _recursiveItems(this);
280
+ ret.push(level);
281
+ });
282
+
283
+ return ret;
284
+
285
+ function _recursiveItems(item) {
286
+ var id = ($(item).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
287
+ if (id) {
288
+ var currentItem = {"id" : id[2]};
289
+ if ($(item).children(o.listType).children(o.items).length > 0) {
290
+ currentItem.children = [];
291
+ $(item).children(o.listType).children(o.items).each(function() {
292
+ var level = _recursiveItems(this);
293
+ currentItem.children.push(level);
294
+ });
295
+ }
296
+ return currentItem;
297
+ }
298
+ }
299
+ },
300
+
301
+ toArray: function(options) {
302
+
303
+ var o = $.extend({}, this.options, options),
304
+ sDepth = o.startDepthCount || 0,
305
+ ret = [],
306
+ left = 2;
307
+
308
+ ret.push({
309
+ "item_id": o.rootID,
310
+ "parent_id": 'none',
311
+ "depth": sDepth,
312
+ "left": '1',
313
+ "right": ($(o.items, this.element).length + 1) * 2
314
+ });
315
+
316
+ $(this.element).children(o.items).each(function () {
317
+ left = _recursiveArray(this, sDepth + 1, left);
318
+ });
319
+
320
+ ret = ret.sort(function(a,b){ return (a.left - b.left); });
321
+
322
+ return ret;
323
+
324
+ function _recursiveArray(item, depth, left) {
325
+
326
+ var right = left + 1,
327
+ id,
328
+ pid;
329
+
330
+ if ($(item).children(o.listType).children(o.items).length > 0) {
331
+ depth ++;
332
+ $(item).children(o.listType).children(o.items).each(function () {
333
+ right = _recursiveArray($(this), depth, right);
334
+ });
335
+ depth --;
336
+ }
337
+
338
+ id = ($(item).attr(o.attribute || 'id')).match(o.expression || (/(.+)[-=_](.+)/));
339
+
340
+ if (depth === sDepth + 1) {
341
+ pid = o.rootID;
342
+ } else {
343
+ var parentItem = ($(item).parent(o.listType)
344
+ .parent(o.items)
345
+ .attr(o.attribute || 'id'))
346
+ .match(o.expression || (/(.+)[-=_](.+)/));
347
+ pid = parentItem[2];
348
+ }
349
+
350
+ if (id) {
351
+ ret.push({"item_id": id[2], "parent_id": pid, "depth": depth, "left": left, "right": right});
352
+ }
353
+
354
+ left = right + 1;
355
+ return left;
356
+ }
357
+
358
+ },
359
+
360
+ _clearEmpty: function(item) {
361
+
362
+ var emptyList = $(item).children(this.options.listType);
363
+ if (emptyList.length && !emptyList.children().length && !this.options.doNotClear) {
364
+ emptyList.remove();
365
+ }
366
+
367
+ },
368
+
369
+ _getLevel: function(item) {
370
+
371
+ var level = 1;
372
+
373
+ if (this.options.listType) {
374
+ var list = item.closest(this.options.listType);
375
+ while (list && list.length > 0 &&
376
+ !list.is('.ui-sortable')) {
377
+ level++;
378
+ list = list.parent().closest(this.options.listType);
379
+ }
380
+ }
381
+
382
+ return level;
383
+ },
384
+
385
+ _getChildLevels: function(parent, depth) {
386
+ var self = this,
387
+ o = this.options,
388
+ result = 0;
389
+ depth = depth || 0;
390
+
391
+ $(parent).children(o.listType).children(o.items).each(function (index, child) {
392
+ result = Math.max(self._getChildLevels(child, depth + 1), result);
393
+ });
394
+
395
+ return depth ? result + 1 : result;
396
+ },
397
+
398
+ _isAllowed: function(parentItem, level, levels) {
399
+ var o = this.options,
400
+ isRoot = $(this.domPosition.parent).hasClass('ui-sortable') ? true : false,
401
+ maxLevels = this.placeholder.closest('.ui-sortable').nestedSortable('option', 'maxLevels'); // this takes into account the maxLevels set to the recipient list
402
+
403
+ // Is the root protected?
404
+ // Are we trying to nest under a no-nest?
405
+ // Are we nesting too deep?
406
+ if (!o.isAllowed(this.currentItem, parentItem) ||
407
+ parentItem && parentItem.hasClass(o.disableNesting) ||
408
+ o.protectRoot && (parentItem == null && !isRoot || isRoot && level > 1)) {
409
+ this.placeholder.addClass(o.errorClass);
410
+ if (maxLevels < levels && maxLevels != 0) {
411
+ this.beyondMaxLevels = levels - maxLevels;
412
+ } else {
413
+ this.beyondMaxLevels = 1;
414
+ }
415
+ } else {
416
+ if (maxLevels < levels && maxLevels != 0) {
417
+ this.placeholder.addClass(o.errorClass);
418
+ this.beyondMaxLevels = levels - maxLevels;
419
+ } else {
420
+ this.placeholder.removeClass(o.errorClass);
421
+ this.beyondMaxLevels = 0;
422
+ }
423
+ }
424
+ }
425
+
426
+ }));
427
+
428
+ $.mjs.nestedSortable.prototype.options = $.extend({}, $.ui.sortable.prototype.options, $.mjs.nestedSortable.prototype.options);
429
+ })(jQuery);
@@ -0,0 +1,45 @@
1
+ #= require jquery
2
+ #= require jquery_ujs
3
+ #= require jquery.ui.sortable
4
+ #= require jquery.mjs.nestedSortable
5
+
6
+ window.rails_admin_nested_set = (tree_config) ->
7
+ show_flash = (data)->
8
+ $flash = $('<div>')
9
+ .addClass('nestable-flash alert')
10
+ .append($('<button>').addClass('close').data('dismiss', 'alert').html('&times;'))
11
+ .append($('<span>').addClass('body').html(data))
12
+ $('#rails_admin_nestable').append($flash)
13
+ $flash.fadeIn(200).delay(2000).fadeOut 200, -> $(this).remove()
14
+
15
+ $ ->
16
+ $("#" + tree_config["id"]).nestedSortable
17
+ forcePlaceholderSize: true
18
+ # handle: 'i.dd-handle',
19
+ helper: "clone"
20
+ items: "li"
21
+ maxLevels: tree_config["max_depth"]
22
+ opacity: .6
23
+ placeholder: "dd-placeholder"
24
+ tabSize: 25
25
+ # tolerance: 'pointer',
26
+ # toleranceElement: '.dd3-content',
27
+ update: (event, ui) ->
28
+ $.ajax
29
+ type: "POST"
30
+ dataType: "html"
31
+ url: tree_config["update_url"]
32
+ data:
33
+ id: ui.item.data("id")
34
+ parent_id: ui.item.parent().parent().data("id")
35
+ prev_id: ui.item.prev().data("id")
36
+ next_id: ui.item.next().data("id")
37
+
38
+ error: (xhr, status, error) ->
39
+ # alert error
40
+ # window.location.reload()
41
+ show_flash('Nested Set: fatal error')
42
+
43
+ success: (data) ->
44
+ console.log(data)
45
+ show_flash(data)
@@ -0,0 +1,305 @@
1
+ /**
2
+ * jQuery Nestable
3
+ */
4
+
5
+ .dd {
6
+ position: relative;
7
+ display: block;
8
+ margin: 0;
9
+ padding: 0;
10
+ /* max-width: 600px; */
11
+ list-style: none;
12
+ font-size: 13px;
13
+ line-height: 20px;
14
+ }
15
+
16
+ .dd-list, .dd-list ol {
17
+ display: block;
18
+ position: relative;
19
+ margin: 0;
20
+ padding: 0;
21
+ list-style: none;
22
+ }
23
+
24
+ .dd-list ol {
25
+ padding-left: 30px;
26
+ }
27
+
28
+ .dd-collapsed ol {
29
+ display: none;
30
+ }
31
+
32
+ .dd-item,
33
+ .dd-empty,
34
+ .dd-placeholder {
35
+ display: block;
36
+ position: relative;
37
+ margin: 0;
38
+ padding: 0;
39
+ min-height: 20px;
40
+ font-size: 13px;
41
+ line-height: 20px;
42
+ }
43
+
44
+ .dd-handle {
45
+ display: block;
46
+ height: 30px;
47
+ margin: 5px 0;
48
+ padding: 5px 10px;
49
+ color: #333;
50
+ text-decoration: none;
51
+ font-weight: bold;
52
+ border: 1px solid #ccc;
53
+ background: #fafafa;
54
+ background: -webkit-linear-gradient(top, #fafafa 0%, #eee 100%);
55
+ background: -moz-linear-gradient(top, #fafafa 0%, #eee 100%);
56
+ background: linear-gradient(top, #fafafa 0%, #eee 100%);
57
+ -webkit-border-radius: 3px;
58
+ border-radius: 3px;
59
+ box-sizing: border-box;
60
+ -moz-box-sizing: border-box;
61
+ }
62
+
63
+ .dd-handle:hover {
64
+ color: #2ea8e5;
65
+ background: #fff;
66
+ }
67
+
68
+ .dd-item > button {
69
+ display: block;
70
+ position: relative;
71
+ cursor: pointer;
72
+ float: left;
73
+ width: 25px;
74
+ height: 20px;
75
+ margin: 5px 0;
76
+ padding: 0;
77
+ text-indent: 100%;
78
+ white-space: nowrap;
79
+ overflow: hidden;
80
+ border: 0;
81
+ background: transparent;
82
+ font-size: 12px;
83
+ line-height: 1;
84
+ text-align: center;
85
+ font-weight: bold;
86
+ }
87
+
88
+ .dd-item > button:before {
89
+ content: '+';
90
+ display: block;
91
+ position: absolute;
92
+ width: 100%;
93
+ text-align: center;
94
+ text-indent: 0;
95
+ }
96
+
97
+ .dd-item > button[data-action="collapse"]:before {
98
+ content: '-';
99
+ }
100
+
101
+ .dd-placeholder, .dd-empty {
102
+ margin: 5px 0;
103
+ padding: 0;
104
+ min-height: 30px;
105
+ background: #f2fbff;
106
+ border: 1px dashed #b6bcbf;
107
+ box-sizing: border-box;
108
+ -moz-box-sizing: border-box;
109
+ }
110
+
111
+ .dd-empty {
112
+ border: 1px dashed #bbb;
113
+ min-height: 100px;
114
+ background-color: #e5e5e5;
115
+ background-image: -webkit-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff), -webkit-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);
116
+ background-image: -moz-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff), -moz-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);
117
+ background-image: linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff), linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);
118
+ background-size: 60px 60px;
119
+ background-position: 0 0, 30px 30px;
120
+ }
121
+
122
+ .dd-dragel {
123
+ position: absolute;
124
+ pointer-events: none;
125
+ z-index: 9999;
126
+ }
127
+
128
+ .dd-dragel > .dd-item .dd-handle {
129
+ margin-top: 0;
130
+ }
131
+
132
+ .dd-dragel .dd-handle {
133
+ -webkit-box-shadow: 2px 4px 6px 0 rgba(0, 0, 0, .1);
134
+ box-shadow: 2px 4px 6px 0 rgba(0, 0, 0, .1);
135
+ }
136
+
137
+ /**
138
+ * Nestable Extras
139
+ */
140
+
141
+ .nestable-lists {
142
+ display: block;
143
+ clear: both;
144
+ padding: 30px 0;
145
+ width: 100%;
146
+ border: 0;
147
+ border-top: 2px solid #ddd;
148
+ border-bottom: 2px solid #ddd;
149
+ }
150
+
151
+ #nestable-menu {
152
+ padding: 0;
153
+ margin: 20px 0;
154
+ }
155
+
156
+ #nestable-output, #nestable2-output {
157
+ width: 100%;
158
+ height: 7em;
159
+ font-size: 0.75em;
160
+ line-height: 1.333333em;
161
+ font-family: Consolas, monospace;
162
+ padding: 5px;
163
+ box-sizing: border-box;
164
+ -moz-box-sizing: border-box;
165
+ }
166
+
167
+ #nestable2 .dd-handle {
168
+ color: #fff;
169
+ border: 1px solid #999;
170
+ background: #bbb;
171
+ background: -webkit-linear-gradient(top, #bbb 0%, #999 100%);
172
+ background: -moz-linear-gradient(top, #bbb 0%, #999 100%);
173
+ background: linear-gradient(top, #bbb 0%, #999 100%);
174
+ }
175
+
176
+ #nestable2 .dd-handle:hover {
177
+ background: #bbb;
178
+ }
179
+
180
+ #nestable2 .dd-item > button:before {
181
+ color: #fff;
182
+ }
183
+
184
+ @media only screen and (min-width: 700px) {
185
+
186
+ .dd {
187
+ float: none;
188
+ width: auto;
189
+ }
190
+ .dd + .dd {
191
+ margin-left: 2%;
192
+ }
193
+
194
+ }
195
+
196
+ .dd-hover > .dd-handle {
197
+ background: #2ea8e5 !important;
198
+ }
199
+
200
+ /**
201
+ * Nestable Draggable Handles
202
+ */
203
+
204
+ .dd3-content {
205
+ display: block;
206
+ height: 30px;
207
+ margin: 5px 0;
208
+ padding: 5px 10px 5px 40px;
209
+ color: #333;
210
+ text-decoration: none;
211
+ font-weight: bold;
212
+ border: 1px solid #ccc;
213
+ background: #fafafa;
214
+ background: -webkit-linear-gradient(top, #fafafa 0%, #eee 100%);
215
+ background: -moz-linear-gradient(top, #fafafa 0%, #eee 100%);
216
+ background: linear-gradient(top, #fafafa 0%, #eee 100%);
217
+ -webkit-border-radius: 3px;
218
+ border-radius: 3px;
219
+ box-sizing: border-box;
220
+ -moz-box-sizing: border-box;
221
+ }
222
+
223
+ .dd3-content:hover {
224
+ color: #2ea8e5;
225
+ background: #fff;
226
+ }
227
+
228
+ .dd-dragel > .dd3-item > .dd3-content {
229
+ margin: 0;
230
+ }
231
+
232
+ .dd3-item > button {
233
+ margin-left: 30px;
234
+ }
235
+
236
+ .dd3-handle {
237
+ position: absolute;
238
+ margin: 0;
239
+ left: 0;
240
+ top: 0;
241
+ cursor: pointer;
242
+ width: 30px;
243
+ text-indent: 100px;
244
+ white-space: nowrap;
245
+ overflow: hidden;
246
+ border: 1px solid #aaa;
247
+ background: #ddd;
248
+ background: -webkit-linear-gradient(top, #ddd 0%, #bbb 100%);
249
+ background: -moz-linear-gradient(top, #ddd 0%, #bbb 100%);
250
+ background: linear-gradient(top, #ddd 0%, #bbb 100%);
251
+ border-top-right-radius: 0;
252
+ border-bottom-right-radius: 0;
253
+ }
254
+
255
+ .dd3-handle:before {
256
+ content: '≡';
257
+ display: block;
258
+ position: absolute;
259
+ left: 0;
260
+ top: 3px;
261
+ width: 100%;
262
+ text-align: center;
263
+ text-indent: 0;
264
+ color: #fff;
265
+ font-size: 20px;
266
+ font-weight: normal;
267
+ }
268
+
269
+ .dd3-handle:hover {
270
+ background: #ddd;
271
+ }
272
+
273
+ .dd3-content .links {
274
+ width: auto;
275
+ }
276
+
277
+ .dd3-content ul.inline.actions {
278
+ margin: 0;
279
+ padding: 0;
280
+ list-style: none;
281
+ display: block
282
+ }
283
+
284
+ .dd3-content ul.inline.actions li a {
285
+ width: 14px;
286
+ height: 16px;
287
+ text-decoration: none;
288
+ }
289
+
290
+ .dd3-content ul.inline.actions li a:hover {
291
+ text-decoration: none;
292
+ }
293
+
294
+ #rails_admin_nestable {
295
+ position: relative;
296
+ }
297
+
298
+ .nestable-flash {
299
+ position: fixed;
300
+ top: 80px;
301
+ width: auto;
302
+ right: 20px;
303
+ display: none;
304
+ min-width: 150px;
305
+ }
@@ -0,0 +1,3 @@
1
+ .row-fluid
2
+ .span12#rails_admin_nestable
3
+ = rails_admin_nested_set @nodes
@@ -0,0 +1,6 @@
1
+ en:
2
+ admin:
3
+ actions:
4
+ nested_set:
5
+ menu: "Sorting"
6
+ breadcrumb: "Sorting"
@@ -0,0 +1,6 @@
1
+ ru:
2
+ admin:
3
+ actions:
4
+ nested_set:
5
+ menu: "Сортировка"
6
+ breadcrumb: "Сортировка"
@@ -0,0 +1,20 @@
1
+ module RailsAdminNestedSet
2
+ class Configuration
3
+ def initialize(abstract_model)
4
+ @abstract_model = abstract_model
5
+ end
6
+
7
+ def options
8
+ @options ||= {
9
+ max_depth: 1,
10
+ }.merge(config)
11
+
12
+ @options || {}
13
+ end
14
+
15
+ protected
16
+ def config
17
+ ::RailsAdmin::Config.model(@abstract_model.model).nested_set
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ module RailsAdminNestedSet
2
+ class Engine < ::Rails::Engine
3
+ initializer 'Include RailsAdminNestedSet::Helper' do |app|
4
+ ActionView::Base.send :include, RailsAdminNestedSet::Helper
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,45 @@
1
+ module RailsAdminNestedSet
2
+ module Helper
3
+ def rails_admin_nested_set(tree, opts= {})
4
+ tree = tree.to_a.sort_by { |m| m.lft }
5
+ roots = tree.select{|elem| elem.parent_id.nil?}
6
+ id = "ns_#{rand(100_000_000..999_999_999)}"
7
+ content = content_tag(:ol, rails_admin_nested_set_builder(roots, tree), id: id, class: 'dd-list')
8
+ js = "rails_admin_nested_set({id: '#{id}', max_depth: #{max_depth}, update_url: '#{nested_set_path(model_name: @abstract_model)}'});"
9
+ content + content_tag(:script, js.html_safe, type: 'text/javascript')
10
+ end
11
+
12
+
13
+ def rails_admin_nested_set_builder(nodes, tree)
14
+ nodes.map do |node|
15
+ li_classes = 'dd-item dd3-item'
16
+
17
+ content_tag :li, class: li_classes, :'data-id' => node.id do
18
+
19
+ output = content_tag :div, 'drag', class: 'dd-handle dd3-handle'
20
+ output+= content_tag :div, class: 'dd3-content' do
21
+ content = link_to @model_config.with(object: node).object_label, edit_path(@abstract_model, node.id)
22
+ content + content_tag(:div, action_links(node), class: 'pull-right links')
23
+ end
24
+
25
+ children = tree.select{|elem| elem.parent_id == node.id}
26
+ if children.any?
27
+ output += content_tag :ol, rails_admin_nested_set_builder(children, tree), class: 'dd-list'
28
+ end
29
+
30
+ output
31
+ end
32
+ end.join.html_safe
33
+ end
34
+
35
+ def max_depth
36
+ @nested_set_conf.options[:max_depth] || '0'
37
+ end
38
+
39
+ def action_links(model)
40
+ content_tag :ul, class: 'inline actions' do
41
+ menu_for :member, @abstract_model, model, true
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,9 @@
1
+ module RailsAdmin
2
+ module Config
3
+ class Model
4
+ register_instance_option :nested_set do
5
+ nil
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,76 @@
1
+ module RailsAdmin
2
+ module Config
3
+ module Actions
4
+ class NestedSet < Base
5
+ RailsAdmin::Config::Actions.register(self)
6
+
7
+ # Is the action acting on the root level (Example: /admin/contact)
8
+ register_instance_option :root? do
9
+ false
10
+ end
11
+
12
+ register_instance_option :collection? do
13
+ true
14
+ end
15
+
16
+ # Is the action on an object scope (Example: /admin/team/1/edit)
17
+ register_instance_option :member? do
18
+ false
19
+ end
20
+
21
+ register_instance_option :controller do
22
+ Proc.new do |klass|
23
+ @nested_set_conf = ::RailsAdminNestedSet::Configuration.new @abstract_model
24
+
25
+ if params['id'].present?
26
+ p params
27
+ begin
28
+ id = params[:id].to_s
29
+ parent_id = params[:parent_id].to_s
30
+ prev_id = params[:prev_id].to_s
31
+ next_id = params[:next_id].to_s
32
+
33
+ if id.empty?
34
+ return render text: 'Nested set UI error: node id not defined', status: 500
35
+ elsif parent_id.empty? && prev_id.empty? && next_id.empty?
36
+ return render text: 'Nested set UI error: not defined where to move node', status: 500
37
+ end
38
+
39
+
40
+ obj = @abstract_model.model.find(id)
41
+ if prev_id.empty? && next_id.empty?
42
+ p "move_to_child_of #{parent_id}"
43
+ obj.move_to_child_of @abstract_model.model.find(parent_id)
44
+ elsif !prev_id.empty?
45
+ p "move_to_right_of #{prev_id}"
46
+ obj.move_to_right_of @abstract_model.model.find(prev_id)
47
+ elsif !next_id.empty?
48
+ p "move_to_left_of #{next_id}"
49
+ obj.move_to_left_of @abstract_model.model.find(next_id)
50
+ end
51
+
52
+ message = "<strong>#{I18n.t('admin.actions.nestable.success')}!</strong>"
53
+ rescue Exception => e
54
+ @abstract_model.model.rebuild!
55
+ message = "<strong>#{I18n.t('admin.actions.nestable.error')}</strong>: #{e}"
56
+ end
57
+
58
+ render text: message
59
+ else
60
+ @nodes = list_entries(@model_config, :index, nil, nil).order('lft')
61
+ render action: @action.template_name
62
+ end
63
+ end
64
+ end
65
+
66
+ register_instance_option :link_icon do
67
+ 'icon-move'
68
+ end
69
+
70
+ register_instance_option :http_methods do
71
+ [:get, :post]
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,3 @@
1
+ module RailsAdminNestedSet
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,14 @@
1
+ module RailsAdminNestedSet
2
+
3
+ end
4
+
5
+ require "rails_admin_nested_set/version"
6
+ require 'rails_admin_nested_set/engine'
7
+
8
+ require 'rails_admin/config/actions'
9
+ require 'rails_admin/config/model'
10
+
11
+ require 'rails_admin_nested_set/configuration'
12
+ require 'rails_admin_nested_set/nested_set'
13
+ require 'rails_admin_nested_set/model'
14
+ require 'rails_admin_nested_set/helper'
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rails_admin_nested_set/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "rails_admin_nested_set"
8
+ gem.version = RailsAdminNestedSet::VERSION
9
+ gem.authors = ["Gleb Tv"]
10
+ gem.email = ["glebtv@gmail.com"]
11
+ gem.description = %q{Rails admin nested set}
12
+ gem.summary = %q{Interface for editing a nested set for rails_admin}
13
+ gem.homepage = "https://github.com/rs-pro/rails_admin_nested_set"
14
+
15
+ gem.add_dependency "rails", ">= 3.1"
16
+ gem.add_dependency "jquery-rails"
17
+ gem.add_dependency "jquery-ui-rails"
18
+
19
+ gem.files = `git ls-files`.split($/)
20
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
21
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
22
+ gem.require_paths = ["lib"]
23
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_admin_nested_set
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Gleb Tv
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '3.1'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '3.1'
30
+ - !ruby/object:Gem::Dependency
31
+ name: jquery-rails
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: jquery-ui-rails
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Rails admin nested set
63
+ email:
64
+ - glebtv@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - LICENSE.txt
72
+ - README.md
73
+ - Rakefile
74
+ - app/assets/javascripts/jquery.mjs.nestedSortable.js
75
+ - app/assets/javascripts/rails_admin_nested_set.js.coffee
76
+ - app/assets/stylesheets/rails_admin_nested_set.css.scss
77
+ - app/views/rails_admin/main/nested_set.html.haml
78
+ - config/locales/en.nested_set.yml
79
+ - config/locales/ru.nested_set.yml
80
+ - lib/rails_admin_nested_set.rb
81
+ - lib/rails_admin_nested_set/configuration.rb
82
+ - lib/rails_admin_nested_set/engine.rb
83
+ - lib/rails_admin_nested_set/helper.rb
84
+ - lib/rails_admin_nested_set/model.rb
85
+ - lib/rails_admin_nested_set/nested_set.rb
86
+ - lib/rails_admin_nested_set/version.rb
87
+ - rails_admin_nested_set.gemspec
88
+ homepage: https://github.com/rs-pro/rails_admin_nested_set
89
+ licenses: []
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 1.8.24
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: Interface for editing a nested set for rails_admin
112
+ test_files: []