rails_admin_nested_set 0.0.1

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