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.
- checksums.yaml +7 -0
- data/Changelog.md +75 -0
- data/MIT-LICENSE +20 -0
- data/README.md +254 -0
- data/app/assets/javascripts/active_admin/sortable.js +2 -0
- data/app/assets/javascripts/active_admin/sortable_core.js +109 -0
- data/app/assets/stylesheets/active_admin/sortable.sass +78 -0
- data/lib/active_admin/sortable_tree.rb +5 -0
- data/lib/active_admin/sortable_tree/compatibility.rb +11 -0
- data/lib/active_admin/sortable_tree/controller_actions.rb +86 -0
- data/lib/active_admin/sortable_tree/engine.rb +9 -0
- data/lib/active_admin/sortable_tree/version.rb +5 -0
- data/lib/active_admin/views/index_as_block_decorator.rb +30 -0
- data/lib/active_admin/views/index_as_sortable.rb +180 -0
- data/lib/activeadmin-sortable-tree.rb +1 -0
- data/lib/rs_activeadmin-sortable-tree.rb +1 -0
- data/vendor/assets/javascripts/jquery.mjs.nestedSortable.js +907 -0
- metadata +204 -0
|
@@ -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,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,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
|
+
}));
|