rs-active_admin-sortable_tree 2.1.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.
@@ -0,0 +1,78 @@
1
+ $cOddRowBackground: #f4f5f5
2
+ $cRowBorder: #e8e8e8
3
+ $cRowSelected: #d9e4ec
4
+ $cRowError: rgb(255,87,87)
5
+
6
+ @import "active_admin/mixins/utilities"
7
+
8
+ body.active_admin
9
+ .disclose
10
+ cursor: pointer
11
+ width: 10px
12
+ display: none
13
+
14
+ .index_as_sortable
15
+ .resource_selection_toggle_panel
16
+ padding-left: 12px
17
+
18
+ > ol
19
+ margin: 16px 0
20
+
21
+ ol
22
+ list-style-type: none
23
+ padding: 0
24
+
25
+ li
26
+ cursor: default !important
27
+
28
+ &.placeholder
29
+ height: 3em
30
+ background: lighten($cOddRowBackground, 10%)
31
+ border: 1px dashed $cRowSelected
32
+ box-sizing: border-box
33
+
34
+ &.cantdoit
35
+ border: 1px dashed $cRowError
36
+
37
+ .item
38
+ width: 100%
39
+ border-top: 1px solid $cRowBorder
40
+ border-bottom: 1px solid $cRowBorder
41
+ +clearfix
42
+
43
+ &.even
44
+ background: white
45
+
46
+ &.odd
47
+ background: $cOddRowBackground
48
+
49
+ .cell
50
+ margin: 0
51
+ padding: 10px 12px 8px 12px
52
+
53
+ h3.cell
54
+ font-size: 16px
55
+ line-height: 14px
56
+ color: black
57
+
58
+ &.ui-sortable
59
+ li .item:hover
60
+ cursor: move
61
+ background-color: $cRowSelected
62
+
63
+ > li > ol
64
+ margin-left: 30px
65
+
66
+ li.mjs-nestedSortable-collapsed > ol
67
+ display: none
68
+
69
+ li.mjs-nestedSortable-branch > div > .disclose
70
+ display: block
71
+ float: left
72
+ padding: 10px 5px 8px 5px
73
+
74
+ li.mjs-nestedSortable-collapsed > div > .disclose > span:before
75
+ content: '+ '
76
+
77
+ li.mjs-nestedSortable-expanded > div > .disclose > span:before
78
+ content: '- '
@@ -0,0 +1,5 @@
1
+ require 'active_admin/sortable_tree/compatibility'
2
+ require 'active_admin/sortable_tree/engine'
3
+ require 'active_admin/sortable_tree/controller_actions'
4
+ require 'active_admin/views/index_as_sortable'
5
+ require 'active_admin/views/index_as_block_decorator'
@@ -0,0 +1,11 @@
1
+ module ActiveAdmin::SortableTree
2
+ class Compatibility
3
+ def self.normalized_resource_name(resource_name)
4
+ if Rails::VERSION::MAJOR >= 5
5
+ resource_name.to_s.underscore.parameterize(separator: "_".freeze)
6
+ else
7
+ resource_name.to_s.underscore.parameterize("_".freeze)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,86 @@
1
+ module ActiveAdmin::SortableTree
2
+ module ControllerActions
3
+
4
+ attr_accessor :sortable_options
5
+
6
+ def sortable(options = {})
7
+ options.reverse_merge! :sorting_attribute => :position,
8
+ :parent_method => :parent,
9
+ :children_method => :children,
10
+ :roots_method => :roots,
11
+ :tree => false,
12
+ :max_levels => 0,
13
+ :protect_root => false,
14
+ :collapsible => false, #hides +/- buttons
15
+ :start_collapsed => false,
16
+ :sortable => true
17
+
18
+ # BAD BAD BAD FIXME: don't pollute original class
19
+ @sortable_options = options
20
+
21
+ # disable pagination
22
+ config.paginate = false
23
+
24
+ collection_action :sort, :method => :post do
25
+ resource_name = ActiveAdmin::SortableTree::Compatibility.normalized_resource_name(active_admin_config.resource_name)
26
+
27
+
28
+ errors = []
29
+ if options[:tree]
30
+ if params['id'].present?
31
+ begin
32
+ id = params[:id].to_s
33
+ parent_id = params[:parent_id].to_s
34
+ prev_id = params[:prev_id].to_s
35
+ next_id = params[:next_id].to_s
36
+
37
+ if id.empty?
38
+ return render plain: 'Nested set UI error: node id not defined', status: 500
39
+ elsif parent_id.empty? && prev_id.empty? && next_id.empty?
40
+ return render plain: 'Nested set UI error: not defined where to move node', status: 500
41
+ end
42
+
43
+
44
+ obj = resource_class.find(id)
45
+ if prev_id.empty? && next_id.empty?
46
+ obj.move_to_child_of resource_class.find(parent_id)
47
+ elsif !prev_id.empty?
48
+ obj.move_to_right_of resource_class.find(prev_id)
49
+ elsif !next_id.empty?
50
+ obj.move_to_left_of resource_class.find(next_id)
51
+ end
52
+
53
+ message = "<strong>#{I18n.t('admin.actions.nested_set.success')}!</strong>"
54
+ render plain: message
55
+ rescue Exception => e
56
+ resource_class.model.rebuild!
57
+ Rails.logger.error("#{e.class.name}: #{e.message}:\n#{e.backtrace.join("\n")}")
58
+ render plain: "<strong>#{I18n.t('admin.actions.nested_set.error')}</strong>: #{e}", status: 500
59
+ end
60
+ else
61
+ render plain: "<strong>#{I18n.t('admin.actions.nested_set.error')}</strong>: no id", status: 500
62
+ end
63
+ else
64
+ records = []
65
+ params[resource_name].each_pair do |resource, parent_resource|
66
+ parent_resource = resource_class.find(parent_resource) rescue nil
67
+ records << [resource_class.find(resource), parent_resource]
68
+ end
69
+ ActiveRecord::Base.transaction do
70
+ records.each_with_index do |(record, parent_record), position|
71
+ record.send "#{options[:sorting_attribute]}=", position
72
+ errors << {record.id => record.errors} if !record.save
73
+ end
74
+ end
75
+ if errors.empty?
76
+ head 200
77
+ else
78
+ render json: errors, status: 422
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ ::ActiveAdmin::ResourceDSL.send(:include, ControllerActions)
86
+ end
@@ -0,0 +1,9 @@
1
+ require "activeadmin"
2
+
3
+ module ActiveAdmin
4
+ module SortableTree
5
+ class Engine < ::Rails::Engine
6
+ engine_name "active_admin-sortable_tree"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveAdmin
2
+ module SortableTree
3
+ VERSION = "2.1.0"
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ module ActiveAdmin
2
+ module Views
3
+ IndexAsBlock.class_eval do
4
+
5
+ def build(page_presenter, collection)
6
+ add_class "index"
7
+ if active_admin_config.dsl.sortable_options
8
+ set_attribute "data-sortable-type", "plain"
9
+
10
+ sort_url = if (( sort_url_block = active_admin_config.dsl.sortable_options[:sort_url] ))
11
+ sort_url_block.call(self)
12
+ else
13
+ url_for(:action => :sort)
14
+ end
15
+
16
+ set_attribute "data-sortable-url", sort_url
17
+ collection = collection.sort_by do |a|
18
+ a.send(active_admin_config.dsl.sortable_options[:sorting_attribute]) || 1
19
+ end
20
+ end
21
+ resource_selection_toggle_panel if active_admin_config.batch_actions.any?
22
+ collection.each do |obj|
23
+ instance_exec(obj, &page_presenter.block)
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,180 @@
1
+ module ActiveAdmin
2
+ module Views
3
+
4
+ # = Index as a Sortable List or Tree
5
+ class IndexAsSortable < ActiveAdmin::Component
6
+ def build(page_presenter, collection)
7
+ @page_presenter = page_presenter
8
+ @collection = if tree?
9
+ roots
10
+ else
11
+ collection
12
+ end
13
+ @collection = @collection.sort_by do |a|
14
+ a.send(options[:sorting_attribute]) || 1
15
+ end
16
+
17
+ @resource_name = ActiveAdmin::SortableTree::Compatibility.normalized_resource_name(active_admin_config.resource_name)
18
+
19
+ # Call the block passed in. This will set the
20
+ # title and body methods
21
+ instance_eval &page_presenter.block if page_presenter.block
22
+
23
+ add_class "index"
24
+ build_list
25
+ end
26
+
27
+ def self.index_name; "sortable"; end
28
+
29
+ def options
30
+ active_admin_config.dsl.sortable_options
31
+ end
32
+
33
+ def roots
34
+ roots_collection || default_roots_collection
35
+ end
36
+
37
+ # Find the roots by calling the roots method directly on the resource.
38
+ # This effectively performs:
39
+ #
40
+ # TreeNode.roots # => [#<TreeNode id:1>, ... ]
41
+ #
42
+ # Returns collection of roots.
43
+ def default_roots_collection
44
+ resource_class.send(options[:roots_method])
45
+ end
46
+
47
+ # Use user-defined logic to find the root nodes. This executes a callable
48
+ # object within the context of the resource's controller.
49
+ #
50
+ # Example
51
+ #
52
+ # options[:roots_collection] = proc { current_user.tree_nodes.roots }
53
+ #
54
+ # Returns collection of roots.
55
+ def roots_collection
56
+ if (callable = options[:roots_collection])
57
+ controller.instance_exec(&callable)
58
+ end
59
+ end
60
+
61
+ def tree?
62
+ !!options[:tree]
63
+ end
64
+
65
+ # Setter method for the configuration of the label
66
+ def label(method = nil, &block)
67
+ if block_given? || method
68
+ @label = block_given? ? block : method
69
+ end
70
+ @label
71
+ end
72
+
73
+ # Adds links to View, Edit and Delete
74
+ def actions(options = {}, &block)
75
+ options = { :defaults => true }.merge(options)
76
+ @default_actions = options[:defaults]
77
+ @other_actions = block
78
+ end
79
+
80
+ protected
81
+
82
+ def build_list
83
+ resource_selection_toggle_panel if active_admin_config.batch_actions.any?
84
+
85
+ ol sortable_data_options do
86
+ @collection.each do |item|
87
+ build_nested_item(item)
88
+ end
89
+ end
90
+ end
91
+
92
+ def sortable_data_options
93
+ return {} if !sortable?
94
+
95
+ sort_url = if (( sort_url_block = options[:sort_url] ))
96
+ sort_url_block.call(self)
97
+ else
98
+ url_for(:action => :sort)
99
+ end
100
+ {
101
+ "data-sortable-type" => tree? ? "tree" : "list",
102
+ "data-sortable-url" => sort_url,
103
+ "data-max-levels" => options[:max_levels],
104
+ "data-start-collapsed" => options[:start_collapsed],
105
+ "data-protect-root" => options[:protect_root],
106
+ }
107
+ end
108
+
109
+ def sortable?
110
+ if (sortable = options[:sortable]).respond_to? :call
111
+ controller.instance_exec(&sortable)
112
+ else
113
+ sortable
114
+ end
115
+ end
116
+
117
+ def build_nested_item(item)
118
+ li :id => "#{@resource_name}_#{item.id}", 'data-id' => item.id do
119
+
120
+ div :class => "item " << cycle("odd", "even", :name => "list_class") do
121
+ if active_admin_config.batch_actions.any?
122
+ div :class => "cell left" do
123
+ resource_selection_cell(item)
124
+ end
125
+ end
126
+
127
+ if options[:collapsible] && item.send(options[:children_method]).any?
128
+ span :class => :disclose do
129
+ span
130
+ end
131
+ end
132
+
133
+ h3 :class => "cell left" do
134
+ case @label
135
+ when Proc
136
+ self.instance_exec(item, nil, &@label)
137
+ else
138
+ call_method_or_proc_on(item, @label)
139
+ end
140
+ end
141
+ div :class => "cell right" do
142
+ build_actions(item)
143
+ end
144
+ end
145
+
146
+ ol do
147
+ q = item.send(options[:children_method])
148
+ if defined?(Mongoid)
149
+ q = q.order_by(options[:sorting_attribute] => :asc)
150
+ else
151
+ q = q.order(options[:sorting_attribute])
152
+ end
153
+ q.each do |c|
154
+ build_nested_item(c)
155
+ end
156
+ end if tree?
157
+ end
158
+ end
159
+
160
+ def build_actions(resource)
161
+ links = ''.html_safe
162
+ if @default_actions
163
+ if controller.action_methods.include?('show') && authorized?(ActiveAdmin::Auth::READ, resource)
164
+ links << link_to(I18n.t('active_admin.view'), resource_path(resource), :class => "member_link view_link")
165
+ end
166
+ if controller.action_methods.include?('edit') && authorized?(ActiveAdmin::Auth::UPDATE, resource)
167
+ links << link_to(I18n.t('active_admin.edit'), edit_resource_path(resource), :class => "member_link edit_link")
168
+ end
169
+ if controller.action_methods.include?('destroy') && authorized?(ActiveAdmin::Auth::DESTROY, resource)
170
+ links << link_to(I18n.t('active_admin.delete'), resource_path(resource), :method => :delete, :data => {:confirm => I18n.t('active_admin.delete_confirmation')}, :class => "member_link delete_link")
171
+ end
172
+ end
173
+ links << instance_exec(resource, &@other_actions) if @other_actions
174
+ links
175
+ end
176
+
177
+ end
178
+ end
179
+ end
180
+
@@ -0,0 +1 @@
1
+ require 'active_admin/sortable'
@@ -0,0 +1 @@
1
+ require 'active_admin/sortable'
@@ -0,0 +1,907 @@
1
+ /*
2
+ * jQuery UI Nested Sortable
3
+ * v 2.1a / 2016-02-04
4
+ * https://github.com/ilikenwf/nestedSortable
5
+ *
6
+ * Depends on:
7
+ * jquery.ui.sortable.js 1.10+
8
+ *
9
+ * Copyright (c) 2010-2016 Manuele J Sarfatti and contributors
10
+ * Licensed under the MIT License
11
+ * http://www.opensource.org/licenses/mit-license.php
12
+ */
13
+ (function( factory ) {
14
+ "use strict";
15
+
16
+ if ( typeof define === "function" && define.amd ) {
17
+
18
+ // AMD. Register as an anonymous module.
19
+ define([
20
+ "jquery",
21
+ "jquery-ui/sortable"
22
+ ], factory );
23
+ } else {
24
+
25
+ // Browser globals
26
+ factory( window.jQuery );
27
+ }
28
+ }(function($) {
29
+ "use strict";
30
+
31
+ function isOverAxis( x, reference, size ) {
32
+ return ( x > reference ) && ( x < ( reference + size ) );
33
+ }
34
+
35
+ $.widget("mjs.nestedSortable", $.extend({}, $.ui.sortable.prototype, {
36
+
37
+ options: {
38
+ disableParentChange: false,
39
+ doNotClear: false,
40
+ expandOnHover: 700,
41
+ isAllowed: function() { return true; },
42
+ isTree: false,
43
+ listType: "ol",
44
+ maxLevels: 0,
45
+ protectRoot: false,
46
+ rootID: null,
47
+ rtl: false,
48
+ startCollapsed: false,
49
+ tabSize: 20,
50
+
51
+ branchClass: "mjs-nestedSortable-branch",
52
+ collapsedClass: "mjs-nestedSortable-collapsed",
53
+ disableNestingClass: "mjs-nestedSortable-no-nesting",
54
+ errorClass: "mjs-nestedSortable-error",
55
+ expandedClass: "mjs-nestedSortable-expanded",
56
+ hoveringClass: "mjs-nestedSortable-hovering",
57
+ leafClass: "mjs-nestedSortable-leaf",
58
+ disabledClass: "mjs-nestedSortable-disabled"
59
+ },
60
+
61
+ _create: function() {
62
+ var self = this,
63
+ err;
64
+
65
+ this.element.data("ui-sortable", this.element.data("mjs-nestedSortable"));
66
+
67
+ // mjs - prevent browser from freezing if the HTML is not correct
68
+ if (!this.element.is(this.options.listType)) {
69
+ err = "nestedSortable: " +
70
+ "Please check that the listType option is set to your actual list type";
71
+
72
+ throw new Error(err);
73
+ }
74
+
75
+ // if we have a tree with expanding/collapsing functionality,
76
+ // force 'intersect' tolerance method
77
+ if (this.options.isTree && this.options.expandOnHover) {
78
+ this.options.tolerance = "intersect";
79
+ }
80
+
81
+ $.ui.sortable.prototype._create.apply(this, arguments);
82
+
83
+ // prepare the tree by applying the right classes
84
+ // (the CSS is responsible for actual hide/show functionality)
85
+ if (this.options.isTree) {
86
+ $(this.items).each(function() {
87
+ var $li = this.item,
88
+ hasCollapsedClass = $li.hasClass(self.options.collapsedClass),
89
+ hasExpandedClass = $li.hasClass(self.options.expandedClass);
90
+
91
+ if ($li.children(self.options.listType).length) {
92
+ $li.addClass(self.options.branchClass);
93
+ // expand/collapse class only if they have children
94
+
95
+ if ( !hasCollapsedClass && !hasExpandedClass ) {
96
+ if (self.options.startCollapsed) {
97
+ $li.addClass(self.options.collapsedClass);
98
+ } else {
99
+ $li.addClass(self.options.expandedClass);
100
+ }
101
+ }
102
+ } else {
103
+ $li.addClass(self.options.leafClass);
104
+ }
105
+ });
106
+ }
107
+ },
108
+
109
+ _destroy: function() {
110
+ this.element
111
+ .removeData("mjs-nestedSortable")
112
+ .removeData("ui-sortable");
113
+ return $.ui.sortable.prototype._destroy.apply(this, arguments);
114
+ },
115
+
116
+ _mouseDrag: function(event) {
117
+ var i,
118
+ item,
119
+ itemElement,
120
+ intersection,
121
+ self = this,
122
+ o = this.options,
123
+ scrolled = false,
124
+ $document = $(document),
125
+ previousTopOffset,
126
+ parentItem,
127
+ level,
128
+ childLevels,
129
+ itemAfter,
130
+ itemBefore,
131
+ newList,
132
+ method,
133
+ a,
134
+ previousItem,
135
+ nextItem,
136
+ helperIsNotSibling;
137
+
138
+ //Compute the helpers position
139
+ this.position = this._generatePosition(event);
140
+ this.positionAbs = this._convertPositionTo("absolute");
141
+
142
+ if (!this.lastPositionAbs) {
143
+ this.lastPositionAbs = this.positionAbs;
144
+ }
145
+
146
+ //Do scrolling
147
+ if (this.options.scroll) {
148
+ if (this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {
149
+
150
+ if (
151
+ (
152
+ this.overflowOffset.top +
153
+ this.scrollParent[0].offsetHeight
154
+ ) -
155
+ event.pageY <
156
+ o.scrollSensitivity
157
+ ) {
158
+ scrolled = this.scrollParent.scrollTop() + o.scrollSpeed;
159
+ this.scrollParent.scrollTop(scrolled);
160
+ } else if (
161
+ event.pageY -
162
+ this.overflowOffset.top <
163
+ o.scrollSensitivity
164
+ ) {
165
+ scrolled = this.scrollParent.scrollTop() - o.scrollSpeed;
166
+ this.scrollParent.scrollTop(scrolled);
167
+ }
168
+
169
+ if (
170
+ (
171
+ this.overflowOffset.left +
172
+ this.scrollParent[0].offsetWidth
173
+ ) -
174
+ event.pageX <
175
+ o.scrollSensitivity
176
+ ) {
177
+ scrolled = this.scrollParent.scrollLeft() + o.scrollSpeed;
178
+ this.scrollParent.scrollLeft(scrolled);
179
+ } else if (
180
+ event.pageX -
181
+ this.overflowOffset.left <
182
+ o.scrollSensitivity
183
+ ) {
184
+ scrolled = this.scrollParent.scrollLeft() - o.scrollSpeed;
185
+ this.scrollParent.scrollLeft(scrolled);
186
+ }
187
+
188
+ } else {
189
+
190
+ if (
191
+ event.pageY -
192
+ $document.scrollTop() <
193
+ o.scrollSensitivity
194
+ ) {
195
+ scrolled = $document.scrollTop() - o.scrollSpeed;
196
+ $document.scrollTop(scrolled);
197
+ } else if (
198
+ $(window).height() -
199
+ (
200
+ event.pageY -
201
+ $document.scrollTop()
202
+ ) <
203
+ o.scrollSensitivity
204
+ ) {
205
+ scrolled = $document.scrollTop() + o.scrollSpeed;
206
+ $document.scrollTop(scrolled);
207
+ }
208
+
209
+ if (
210
+ event.pageX -
211
+ $document.scrollLeft() <
212
+ o.scrollSensitivity
213
+ ) {
214
+ scrolled = $document.scrollLeft() - o.scrollSpeed;
215
+ $document.scrollLeft(scrolled);
216
+ } else if (
217
+ $(window).width() -
218
+ (
219
+ event.pageX -
220
+ $document.scrollLeft()
221
+ ) <
222
+ o.scrollSensitivity
223
+ ) {
224
+ scrolled = $document.scrollLeft() + o.scrollSpeed;
225
+ $document.scrollLeft(scrolled);
226
+ }
227
+
228
+ }
229
+
230
+ if (scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
231
+ $.ui.ddmanager.prepareOffsets(this, event);
232
+ }
233
+ }
234
+
235
+ //Regenerate the absolute position used for position checks
236
+ this.positionAbs = this._convertPositionTo("absolute");
237
+
238
+ // mjs - find the top offset before rearrangement,
239
+ previousTopOffset = this.placeholder.offset().top;
240
+
241
+ //Set the helper position
242
+ if (!this.options.axis || this.options.axis !== "y") {
243
+ this.helper[0].style.left = this.position.left + "px";
244
+ }
245
+ if (!this.options.axis || this.options.axis !== "x") {
246
+ this.helper[0].style.top = (this.position.top) + "px";
247
+ }
248
+
249
+ // mjs - check and reset hovering state at each cycle
250
+ this.hovering = this.hovering ? this.hovering : null;
251
+ this.mouseentered = this.mouseentered ? this.mouseentered : false;
252
+
253
+ // mjs - let's start caching some variables
254
+ (function() {
255
+ var _parentItem = this.placeholder.parent().parent();
256
+ if (_parentItem && _parentItem.closest(".ui-sortable").length) {
257
+ parentItem = _parentItem;
258
+ }
259
+ }.call(this));
260
+
261
+ level = this._getLevel(this.placeholder);
262
+ childLevels = this._getChildLevels(this.helper);
263
+ newList = document.createElement(o.listType);
264
+
265
+ //Rearrange
266
+ for (i = this.items.length - 1; i >= 0; i--) {
267
+
268
+ //Cache variables and intersection, continue if no intersection
269
+ item = this.items[i];
270
+ itemElement = item.item[0];
271
+ intersection = this._intersectsWithPointer(item);
272
+ if (!intersection) {
273
+ continue;
274
+ }
275
+
276
+ // Only put the placeholder inside the current Container, skip all
277
+ // items form other containers. This works because when moving
278
+ // an item from one container to another the
279
+ // currentContainer is switched before the placeholder is moved.
280
+ //
281
+ // Without this moving items in "sub-sortables" can cause the placeholder to jitter
282
+ // beetween the outer and inner container.
283
+ if (item.instance !== this.currentContainer) {
284
+ continue;
285
+ }
286
+
287
+ // No action if intersected item is disabled
288
+ // and the element above or below in the direction we're going is also disabled
289
+ if (itemElement.className.indexOf(o.disabledClass) !== -1) {
290
+ // Note: intersection hardcoded direction values from
291
+ // jquery.ui.sortable.js:_intersectsWithPointer
292
+ if (intersection === 2) {
293
+ // Going down
294
+ itemAfter = this.items[i + 1];
295
+ if (itemAfter && itemAfter.item.hasClass(o.disabledClass)) {
296
+ continue;
297
+ }
298
+
299
+ } else if (intersection === 1) {
300
+ // Going up
301
+ itemBefore = this.items[i - 1];
302
+ if (itemBefore && itemBefore.item.hasClass(o.disabledClass)) {
303
+ continue;
304
+ }
305
+ }
306
+ }
307
+
308
+ method = intersection === 1 ? "next" : "prev";
309
+
310
+ // cannot intersect with itself
311
+ // no useless actions that have been done before
312
+ // no action if the item moved is the parent of the item checked
313
+ if (itemElement !== this.currentItem[0] &&
314
+ this.placeholder[method]()[0] !== itemElement &&
315
+ !$.contains(this.placeholder[0], itemElement) &&
316
+ (
317
+ this.options.type === "semi-dynamic" ?
318
+ !$.contains(this.element[0], itemElement) :
319
+ true
320
+ )
321
+ ) {
322
+
323
+ // mjs - we are intersecting an element:
324
+ // trigger the mouseenter event and store this state
325
+ if (!this.mouseentered) {
326
+ $(itemElement).mouseenter();
327
+ this.mouseentered = true;
328
+ }
329
+
330
+ // mjs - if the element has children and they are hidden,
331
+ // show them after a delay (CSS responsible)
332
+ if (o.isTree && $(itemElement).hasClass(o.collapsedClass) && o.expandOnHover) {
333
+ if (!this.hovering) {
334
+ $(itemElement).addClass(o.hoveringClass);
335
+ this.hovering = window.setTimeout(function() {
336
+ $(itemElement)
337
+ .removeClass(o.collapsedClass)
338
+ .addClass(o.expandedClass);
339
+
340
+ self.refreshPositions();
341
+ self._trigger("expand", event, self._uiHash());
342
+ }, o.expandOnHover);
343
+ }
344
+ }
345
+
346
+ this.direction = intersection === 1 ? "down" : "up";
347
+
348
+ // mjs - rearrange the elements and reset timeouts and hovering state
349
+ if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) {
350
+ $(itemElement).mouseleave();
351
+ this.mouseentered = false;
352
+ $(itemElement).removeClass(o.hoveringClass);
353
+ if (this.hovering) {
354
+ window.clearTimeout(this.hovering);
355
+ }
356
+ this.hovering = null;
357
+
358
+ // mjs - do not switch container if
359
+ // it's a root item and 'protectRoot' is true
360
+ // or if it's not a root item but we are trying to make it root
361
+ if (o.protectRoot &&
362
+ !(
363
+ this.currentItem[0].parentNode === this.element[0] &&
364
+ // it's a root item
365
+ itemElement.parentNode !== this.element[0]
366
+ // it's intersecting a non-root item
367
+ )
368
+ ) {
369
+ if (this.currentItem[0].parentNode !== this.element[0] &&
370
+ itemElement.parentNode === this.element[0]
371
+ ) {
372
+
373
+ if ( !$(itemElement).children(o.listType).length) {
374
+ itemElement.appendChild(newList);
375
+ if (o.isTree) {
376
+ $(itemElement)
377
+ .removeClass(o.leafClass)
378
+ .addClass(o.branchClass + " " + o.expandedClass);
379
+ }
380
+ }
381
+
382
+ if (this.direction === "down") {
383
+ a = $(itemElement).prev().children(o.listType);
384
+ } else {
385
+ a = $(itemElement).children(o.listType);
386
+ }
387
+
388
+ if (a[0] !== undefined) {
389
+ this._rearrange(event, null, a);
390
+ }
391
+
392
+ } else {
393
+ this._rearrange(event, item);
394
+ }
395
+ } else if (!o.protectRoot) {
396
+ this._rearrange(event, item);
397
+ }
398
+ } else {
399
+ break;
400
+ }
401
+
402
+ // Clear emtpy ul's/ol's
403
+ this._clearEmpty(itemElement);
404
+
405
+ this._trigger("change", event, this._uiHash());
406
+ break;
407
+ }
408
+ }
409
+
410
+ // mjs - to find the previous sibling in the list,
411
+ // keep backtracking until we hit a valid list item.
412
+ (function() {
413
+ var _previousItem = this.placeholder.prev();
414
+ if (_previousItem.length) {
415
+ previousItem = _previousItem;
416
+ } else {
417
+ previousItem = null;
418
+ }
419
+ }.call(this));
420
+
421
+ if (previousItem != null) {
422
+ while (
423
+ previousItem[0].nodeName.toLowerCase() !== "li" ||
424
+ previousItem[0].className.indexOf(o.disabledClass) !== -1 ||
425
+ previousItem[0] === this.currentItem[0] ||
426
+ previousItem[0] === this.helper[0]
427
+ ) {
428
+ if (previousItem[0].previousSibling) {
429
+ previousItem = $(previousItem[0].previousSibling);
430
+ } else {
431
+ previousItem = null;
432
+ break;
433
+ }
434
+ }
435
+ }
436
+
437
+ // mjs - to find the next sibling in the list,
438
+ // keep stepping forward until we hit a valid list item.
439
+ (function() {
440
+ var _nextItem = this.placeholder.next();
441
+ if (_nextItem.length) {
442
+ nextItem = _nextItem;
443
+ } else {
444
+ nextItem = null;
445
+ }
446
+ }.call(this));
447
+
448
+ if (nextItem != null) {
449
+ while (
450
+ nextItem[0].nodeName.toLowerCase() !== "li" ||
451
+ nextItem[0].className.indexOf(o.disabledClass) !== -1 ||
452
+ nextItem[0] === this.currentItem[0] ||
453
+ nextItem[0] === this.helper[0]
454
+ ) {
455
+ if (nextItem[0].nextSibling) {
456
+ nextItem = $(nextItem[0].nextSibling);
457
+ } else {
458
+ nextItem = null;
459
+ break;
460
+ }
461
+ }
462
+ }
463
+
464
+ this.beyondMaxLevels = 0;
465
+
466
+ // mjs - if the item is moved to the left, send it one level up
467
+ // but only if it's at the bottom of the list
468
+ if (parentItem != null &&
469
+ nextItem == null &&
470
+ !(o.protectRoot && parentItem[0].parentNode == this.element[0]) &&
471
+ (
472
+ o.rtl &&
473
+ (
474
+ this.positionAbs.left +
475
+ this.helper.outerWidth() > parentItem.offset().left +
476
+ parentItem.outerWidth()
477
+ ) ||
478
+ !o.rtl && (this.positionAbs.left < parentItem.offset().left)
479
+ )
480
+ ) {
481
+
482
+ parentItem.after(this.placeholder[0]);
483
+ helperIsNotSibling = !parentItem
484
+ .children(o.listItem)
485
+ .children("li:visible:not(.ui-sortable-helper)")
486
+ .length;
487
+ if (o.isTree && helperIsNotSibling) {
488
+ parentItem
489
+ .removeClass(this.options.branchClass + " " + this.options.expandedClass)
490
+ .addClass(this.options.leafClass);
491
+ }
492
+ if(typeof parentItem !== 'undefined')
493
+ this._clearEmpty(parentItem[0]);
494
+ this._trigger("change", event, this._uiHash());
495
+ // mjs - if the item is below a sibling and is moved to the right,
496
+ // make it a child of that sibling
497
+ } else if (previousItem != null &&
498
+ !previousItem.hasClass(o.disableNestingClass) &&
499
+ (
500
+ previousItem.children(o.listType).length &&
501
+ previousItem.children(o.listType).is(":visible") ||
502
+ !previousItem.children(o.listType).length
503
+ ) &&
504
+ !(o.protectRoot && this.currentItem[0].parentNode === this.element[0]) &&
505
+ (
506
+ o.rtl &&
507
+ (
508
+ this.positionAbs.left +
509
+ this.helper.outerWidth() <
510
+ previousItem.offset().left +
511
+ previousItem.outerWidth() -
512
+ o.tabSize
513
+ ) ||
514
+ !o.rtl &&
515
+ (this.positionAbs.left > previousItem.offset().left + o.tabSize)
516
+ )
517
+ ) {
518
+
519
+ this._isAllowed(previousItem, level, level + childLevels + 1);
520
+
521
+ if (!previousItem.children(o.listType).length) {
522
+ previousItem[0].appendChild(newList);
523
+ if (o.isTree) {
524
+ previousItem
525
+ .removeClass(o.leafClass)
526
+ .addClass(o.branchClass + " " + o.expandedClass);
527
+ }
528
+ }
529
+
530
+ // mjs - if this item is being moved from the top, add it to the top of the list.
531
+ if (previousTopOffset && (previousTopOffset <= previousItem.offset().top)) {
532
+ previousItem.children(o.listType).prepend(this.placeholder);
533
+ } else {
534
+ // mjs - otherwise, add it to the bottom of the list.
535
+ previousItem.children(o.listType)[0].appendChild(this.placeholder[0]);
536
+ }
537
+ if(typeof parentItem !== 'undefined')
538
+ this._clearEmpty(parentItem[0]);
539
+ this._trigger("change", event, this._uiHash());
540
+ } else {
541
+ this._isAllowed(parentItem, level, level + childLevels);
542
+ }
543
+
544
+ //Post events to containers
545
+ this._contactContainers(event);
546
+
547
+ //Interconnect with droppables
548
+ if ($.ui.ddmanager) {
549
+ $.ui.ddmanager.drag(this, event);
550
+ }
551
+
552
+ //Call callbacks
553
+ this._trigger("sort", event, this._uiHash());
554
+
555
+ this.lastPositionAbs = this.positionAbs;
556
+ return false;
557
+
558
+ },
559
+
560
+ _mouseStop: function(event) {
561
+ // mjs - if the item is in a position not allowed, send it back
562
+ if (this.beyondMaxLevels) {
563
+
564
+ this.placeholder.removeClass(this.options.errorClass);
565
+
566
+ if (this.domPosition.prev) {
567
+ $(this.domPosition.prev).after(this.placeholder);
568
+ } else {
569
+ $(this.domPosition.parent).prepend(this.placeholder);
570
+ }
571
+
572
+ this._trigger("revert", event, this._uiHash());
573
+
574
+ }
575
+
576
+ // mjs - clear the hovering timeout, just to be sure
577
+ $("." + this.options.hoveringClass)
578
+ .mouseleave()
579
+ .removeClass(this.options.hoveringClass);
580
+
581
+ this.mouseentered = false;
582
+ if (this.hovering) {
583
+ window.clearTimeout(this.hovering);
584
+ }
585
+ this.hovering = null;
586
+
587
+ this._relocate_event = event;
588
+ this._pid_current = $(this.domPosition.parent).parent().attr("id");
589
+ this._sort_current = this.domPosition.prev ? $(this.domPosition.prev).next().index() : 0;
590
+ $.ui.sortable.prototype._mouseStop.apply(this, arguments); //asybnchronous execution, @see _clear for the relocate event.
591
+ },
592
+
593
+ // mjs - this function is slightly modified
594
+ // to make it easier to hover over a collapsed element and have it expand
595
+ _intersectsWithSides: function(item) {
596
+
597
+ var half = this.options.isTree ? .8 : .5,
598
+ isOverBottomHalf = isOverAxis(
599
+ this.positionAbs.top + this.offset.click.top,
600
+ item.top + (item.height * half),
601
+ item.height
602
+ ),
603
+ isOverTopHalf = isOverAxis(
604
+ this.positionAbs.top + this.offset.click.top,
605
+ item.top - (item.height * half),
606
+ item.height
607
+ ),
608
+ isOverRightHalf = isOverAxis(
609
+ this.positionAbs.left + this.offset.click.left,
610
+ item.left + (item.width / 2),
611
+ item.width
612
+ ),
613
+ verticalDirection = this._getDragVerticalDirection(),
614
+ horizontalDirection = this._getDragHorizontalDirection();
615
+
616
+ if (this.floating && horizontalDirection) {
617
+ return (
618
+ (horizontalDirection === "right" && isOverRightHalf) ||
619
+ (horizontalDirection === "left" && !isOverRightHalf)
620
+ );
621
+ } else {
622
+ return verticalDirection && (
623
+ (verticalDirection === "down" && isOverBottomHalf) ||
624
+ (verticalDirection === "up" && isOverTopHalf)
625
+ );
626
+ }
627
+
628
+ },
629
+
630
+ _contactContainers: function() {
631
+
632
+ if (this.options.protectRoot && this.currentItem[0].parentNode === this.element[0] ) {
633
+ return;
634
+ }
635
+
636
+ $.ui.sortable.prototype._contactContainers.apply(this, arguments);
637
+
638
+ },
639
+
640
+ _clear: function() {
641
+ var i,
642
+ item;
643
+
644
+ $.ui.sortable.prototype._clear.apply(this, arguments);
645
+
646
+ //relocate event
647
+ if (!(this._pid_current === this._uiHash().item.parent().parent().attr("id") &&
648
+ this._sort_current === this._uiHash().item.index())) {
649
+ this._trigger("relocate", this._relocate_event, this._uiHash());
650
+ }
651
+
652
+ // mjs - clean last empty ul/ol
653
+ for (i = this.items.length - 1; i >= 0; i--) {
654
+ item = this.items[i].item[0];
655
+ this._clearEmpty(item);
656
+ }
657
+
658
+ },
659
+
660
+ serialize: function(options) {
661
+
662
+ var o = $.extend({}, this.options, options),
663
+ items = this._getItemsAsjQuery(o && o.connected),
664
+ str = [];
665
+
666
+ $(items).each(function() {
667
+ var res = ($(o.item || this).attr(o.attribute || "id") || "")
668
+ .match(o.expression || (/(.+)[-=_](.+)/)),
669
+ pid = ($(o.item || this).parent(o.listType)
670
+ .parent(o.items)
671
+ .attr(o.attribute || "id") || "")
672
+ .match(o.expression || (/(.+)[-=_](.+)/));
673
+
674
+ if (res) {
675
+ str.push(
676
+ (
677
+ (o.key || res[1]) +
678
+ "[" +
679
+ (o.key && o.expression ? res[1] : res[2]) + "]"
680
+ ) +
681
+ "=" +
682
+ (pid ? (o.key && o.expression ? pid[1] : pid[2]) : o.rootID));
683
+ }
684
+ });
685
+
686
+ if (!str.length && o.key) {
687
+ str.push(o.key + "=");
688
+ }
689
+
690
+ return str.join("&");
691
+
692
+ },
693
+
694
+ toHierarchy: function(options) {
695
+
696
+ var o = $.extend({}, this.options, options),
697
+ ret = [];
698
+
699
+ $(this.element).children(o.items).each(function() {
700
+ var level = _recursiveItems(this);
701
+ ret.push(level);
702
+ });
703
+
704
+ return ret;
705
+
706
+ function _recursiveItems(item) {
707
+ var id = ($(item).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[-=_](.+)/)),
708
+ currentItem;
709
+
710
+ var data = $(item).data();
711
+ if (data.nestedSortableItem) {
712
+ delete data.nestedSortableItem; // Remove the nestedSortableItem object from the data
713
+ }
714
+
715
+ if (id) {
716
+ currentItem = {
717
+ "id": id[2]
718
+ };
719
+
720
+ currentItem = $.extend({}, currentItem, data); // Combine the two objects
721
+
722
+ if ($(item).children(o.listType).children(o.items).length > 0) {
723
+ currentItem.children = [];
724
+ $(item).children(o.listType).children(o.items).each(function() {
725
+ var level = _recursiveItems(this);
726
+ currentItem.children.push(level);
727
+ });
728
+ }
729
+ return currentItem;
730
+ }
731
+ }
732
+ },
733
+
734
+ toArray: function(options) {
735
+
736
+ var o = $.extend({}, this.options, options),
737
+ sDepth = o.startDepthCount || 0,
738
+ ret = [],
739
+ left = 1;
740
+
741
+ if (!o.excludeRoot) {
742
+ ret.push({
743
+ "item_id": o.rootID,
744
+ "parent_id": null,
745
+ "depth": sDepth,
746
+ "left": left,
747
+ "right": ($(o.items, this.element).length + 1) * 2
748
+ });
749
+ left++;
750
+ }
751
+
752
+ $(this.element).children(o.items).each(function() {
753
+ left = _recursiveArray(this, sDepth, left);
754
+ });
755
+
756
+ ret = ret.sort(function(a, b) { return (a.left - b.left); });
757
+
758
+ return ret;
759
+
760
+ function _recursiveArray(item, depth, _left) {
761
+
762
+ var right = _left + 1,
763
+ id,
764
+ pid,
765
+ parentItem;
766
+
767
+ if ($(item).children(o.listType).children(o.items).length > 0) {
768
+ depth++;
769
+ $(item).children(o.listType).children(o.items).each(function() {
770
+ right = _recursiveArray($(this), depth, right);
771
+ });
772
+ depth--;
773
+ }
774
+
775
+ id = ($(item).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[-=_](.+)/));
776
+
777
+ if (depth === sDepth) {
778
+ pid = o.rootID;
779
+ } else {
780
+ parentItem = ($(item).parent(o.listType)
781
+ .parent(o.items)
782
+ .attr(o.attribute || "id"))
783
+ .match(o.expression || (/(.+)[-=_](.+)/));
784
+ pid = parentItem[2];
785
+ }
786
+
787
+ if (id) {
788
+ var data = $(item).children('div').data();
789
+ var itemObj = $.extend( data, {
790
+ "id":id[2],
791
+ "parent_id":pid,
792
+ "depth":depth,
793
+ "left":_left,
794
+ "right":right
795
+ } );
796
+ ret.push( itemObj );
797
+ }
798
+
799
+ _left = right + 1;
800
+ return _left;
801
+ }
802
+
803
+ },
804
+
805
+ _clearEmpty: function (item) {
806
+ function replaceClass(elem, search, replace, swap) {
807
+ if (swap) {
808
+ search = [replace, replace = search][0];
809
+ }
810
+
811
+ $(elem).removeClass(search).addClass(replace);
812
+ }
813
+
814
+ var o = this.options,
815
+ childrenList = $(item).children(o.listType),
816
+ hasChildren = childrenList.has('li').length;
817
+
818
+ var doNotClear =
819
+ o.doNotClear ||
820
+ hasChildren ||
821
+ o.protectRoot && $(item)[0] === this.element[0];
822
+
823
+ if (o.isTree) {
824
+ replaceClass(item, o.branchClass, o.leafClass, doNotClear);
825
+ }
826
+
827
+ if (!doNotClear) {
828
+ childrenList.parent().removeClass(o.expandedClass);
829
+ childrenList.remove();
830
+ }
831
+ },
832
+
833
+ _getLevel: function(item) {
834
+
835
+ var level = 1,
836
+ list;
837
+
838
+ if (this.options.listType) {
839
+ list = item.closest(this.options.listType);
840
+ while (list && list.length > 0 && !list.is(".ui-sortable")) {
841
+ level++;
842
+ list = list.parent().closest(this.options.listType);
843
+ }
844
+ }
845
+
846
+ return level;
847
+ },
848
+
849
+ _getChildLevels: function(parent, depth) {
850
+ var self = this,
851
+ o = this.options,
852
+ result = 0;
853
+ depth = depth || 0;
854
+
855
+ $(parent).children(o.listType).children(o.items).each(function(index, child) {
856
+ result = Math.max(self._getChildLevels(child, depth + 1), result);
857
+ });
858
+
859
+ return depth ? result + 1 : result;
860
+ },
861
+
862
+ _isAllowed: function(parentItem, level, levels) {
863
+ var o = this.options,
864
+ // this takes into account the maxLevels set to the recipient list
865
+ maxLevels = this
866
+ .placeholder
867
+ .closest(".ui-sortable")
868
+ .nestedSortable("option", "maxLevels"),
869
+
870
+ // Check if the parent has changed to prevent it, when o.disableParentChange is true
871
+ oldParent = this.currentItem.parent().parent(),
872
+ disabledByParentchange = o.disableParentChange && (
873
+ //From somewhere to somewhere else, except the root
874
+ typeof parentItem !== 'undefined' && !oldParent.is(parentItem) ||
875
+ typeof parentItem === 'undefined' && oldParent.is("li") //From somewhere to the root
876
+ );
877
+ // mjs - is the root protected?
878
+ // mjs - are we nesting too deep?
879
+ if (
880
+ disabledByParentchange ||
881
+ !o.isAllowed(this.placeholder, parentItem, this.currentItem)
882
+ ) {
883
+ this.placeholder.addClass(o.errorClass);
884
+ if (maxLevels < levels && maxLevels !== 0) {
885
+ this.beyondMaxLevels = levels - maxLevels;
886
+ } else {
887
+ this.beyondMaxLevels = 1;
888
+ }
889
+ } else {
890
+ if (maxLevels < levels && maxLevels !== 0) {
891
+ this.placeholder.addClass(o.errorClass);
892
+ this.beyondMaxLevels = levels - maxLevels;
893
+ } else {
894
+ this.placeholder.removeClass(o.errorClass);
895
+ this.beyondMaxLevels = 0;
896
+ }
897
+ }
898
+ }
899
+
900
+ }));
901
+
902
+ $.mjs.nestedSortable.prototype.options = $.extend(
903
+ {},
904
+ $.ui.sortable.prototype.options,
905
+ $.mjs.nestedSortable.prototype.options
906
+ );
907
+ }));