active_admin-sortable_tree 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +8 -0
- data/Gemfile +10 -0
- data/MIT-LICENSE +20 -0
- data/README.md +163 -0
- data/Rakefile +57 -0
- data/active_admin-sortable_tree.gemspec +30 -0
- data/app/assets/javascripts/active_admin/sortable.js.coffee +91 -0
- data/app/assets/stylesheets/active_admin/sortable.css.sass +79 -0
- data/bin/rails +11 -0
- data/gemfiles/3.2.gemfile +7 -0
- data/gemfiles/4.0.gemfile +10 -0
- data/lib/active_admin/sortable_tree.rb +4 -0
- data/lib/active_admin/sortable_tree/controller_actions.rb +52 -0
- data/lib/active_admin/sortable_tree/engine.rb +19 -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 +154 -0
- data/lib/activeadmin-sortable-tree.rb +1 -0
- data/script/rails +11 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/admin/category.rb +15 -0
- data/spec/dummy/app/admin/category_tree.rb +9 -0
- data/spec/dummy/app/admin/dashboard.rb +5 -0
- data/spec/dummy/app/assets/javascripts/active_admin.js +2 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/javascripts/jquery.simulate.js +314 -0
- data/spec/dummy/app/assets/stylesheets/active_admin.css.scss +17 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/admin_user.rb +6 -0
- data/spec/dummy/app/models/category.rb +4 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +56 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +28 -0
- data/spec/dummy/config/environments/production.rb +64 -0
- data/spec/dummy/config/environments/test.rb +32 -0
- data/spec/dummy/config/initializers/active_admin.rb +231 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/devise.rb +256 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +8 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/devise.en.yml +59 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +61 -0
- data/spec/dummy/db/migrate/20140806024135_devise_create_admin_users.rb +48 -0
- data/spec/dummy/db/migrate/20140806024139_create_active_admin_comments.rb +19 -0
- data/spec/dummy/db/migrate/20140806032156_create_categories.rb +12 -0
- data/spec/dummy/db/schema.rb +58 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/features/sortable_spec.rb +58 -0
- data/spec/rails_helper.rb +65 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/support/wait_for_ajax.rb +19 -0
- data/vendor/assets/javascripts/jquery.mjs.nestedSortable.js +613 -0
- metadata +231 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
class DeviseCreateAdminUsers < ActiveRecord::Migration
|
2
|
+
def migrate(direction)
|
3
|
+
super
|
4
|
+
# Create a default user
|
5
|
+
AdminUser.create!(:email => 'admin@example.com', :password => 'password', :password_confirmation => 'password') if direction == :up
|
6
|
+
end
|
7
|
+
|
8
|
+
def change
|
9
|
+
create_table(:admin_users) do |t|
|
10
|
+
## Database authenticatable
|
11
|
+
t.string :email, null: false, default: ""
|
12
|
+
t.string :encrypted_password, null: false, default: ""
|
13
|
+
|
14
|
+
## Recoverable
|
15
|
+
t.string :reset_password_token
|
16
|
+
t.datetime :reset_password_sent_at
|
17
|
+
|
18
|
+
## Rememberable
|
19
|
+
t.datetime :remember_created_at
|
20
|
+
|
21
|
+
## Trackable
|
22
|
+
t.integer :sign_in_count, default: 0, null: false
|
23
|
+
t.datetime :current_sign_in_at
|
24
|
+
t.datetime :last_sign_in_at
|
25
|
+
t.string :current_sign_in_ip
|
26
|
+
t.string :last_sign_in_ip
|
27
|
+
|
28
|
+
## Confirmable
|
29
|
+
# t.string :confirmation_token
|
30
|
+
# t.datetime :confirmed_at
|
31
|
+
# t.datetime :confirmation_sent_at
|
32
|
+
# t.string :unconfirmed_email # Only if using reconfirmable
|
33
|
+
|
34
|
+
## Lockable
|
35
|
+
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
|
36
|
+
# t.string :unlock_token # Only if unlock strategy is :email or :both
|
37
|
+
# t.datetime :locked_at
|
38
|
+
|
39
|
+
|
40
|
+
t.timestamps
|
41
|
+
end
|
42
|
+
|
43
|
+
add_index :admin_users, :email, unique: true
|
44
|
+
add_index :admin_users, :reset_password_token, unique: true
|
45
|
+
# add_index :admin_users, :confirmation_token, unique: true
|
46
|
+
# add_index :admin_users, :unlock_token, unique: true
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class CreateActiveAdminComments < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :active_admin_comments do |t|
|
4
|
+
t.string :namespace
|
5
|
+
t.text :body
|
6
|
+
t.string :resource_id, :null => false
|
7
|
+
t.string :resource_type, :null => false
|
8
|
+
t.references :author, :polymorphic => true
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
add_index :active_admin_comments, [:namespace]
|
12
|
+
add_index :active_admin_comments, [:author_type, :author_id]
|
13
|
+
add_index :active_admin_comments, [:resource_type, :resource_id]
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.down
|
17
|
+
drop_table :active_admin_comments
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# This file is auto-generated from the current state of the database. Instead
|
3
|
+
# of editing this file, please use the migrations feature of Active Record to
|
4
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
5
|
+
#
|
6
|
+
# Note that this schema.rb definition is the authoritative source for your
|
7
|
+
# database schema. If you need to create the application database on another
|
8
|
+
# system, you should be using db:schema:load, not running all the migrations
|
9
|
+
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
10
|
+
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
11
|
+
#
|
12
|
+
# It's strongly recommended to check this file into your version control system.
|
13
|
+
|
14
|
+
ActiveRecord::Schema.define(:version => 20140806032156) do
|
15
|
+
|
16
|
+
create_table "active_admin_comments", :force => true do |t|
|
17
|
+
t.string "namespace"
|
18
|
+
t.text "body"
|
19
|
+
t.string "resource_id", :null => false
|
20
|
+
t.string "resource_type", :null => false
|
21
|
+
t.integer "author_id"
|
22
|
+
t.string "author_type"
|
23
|
+
t.datetime "created_at", :null => false
|
24
|
+
t.datetime "updated_at", :null => false
|
25
|
+
end
|
26
|
+
|
27
|
+
add_index "active_admin_comments", ["author_type", "author_id"], :name => "index_active_admin_comments_on_author_type_and_author_id"
|
28
|
+
add_index "active_admin_comments", ["namespace"], :name => "index_active_admin_comments_on_namespace"
|
29
|
+
add_index "active_admin_comments", ["resource_type", "resource_id"], :name => "index_active_admin_comments_on_resource_type_and_resource_id"
|
30
|
+
|
31
|
+
create_table "admin_users", :force => true do |t|
|
32
|
+
t.string "email", :default => "", :null => false
|
33
|
+
t.string "encrypted_password", :default => "", :null => false
|
34
|
+
t.string "reset_password_token"
|
35
|
+
t.datetime "reset_password_sent_at"
|
36
|
+
t.datetime "remember_created_at"
|
37
|
+
t.integer "sign_in_count", :default => 0, :null => false
|
38
|
+
t.datetime "current_sign_in_at"
|
39
|
+
t.datetime "last_sign_in_at"
|
40
|
+
t.string "current_sign_in_ip"
|
41
|
+
t.string "last_sign_in_ip"
|
42
|
+
t.datetime "created_at", :null => false
|
43
|
+
t.datetime "updated_at", :null => false
|
44
|
+
end
|
45
|
+
|
46
|
+
add_index "admin_users", ["email"], :name => "index_admin_users_on_email", :unique => true
|
47
|
+
add_index "admin_users", ["reset_password_token"], :name => "index_admin_users_on_reset_password_token", :unique => true
|
48
|
+
|
49
|
+
create_table "categories", :force => true do |t|
|
50
|
+
t.string "name"
|
51
|
+
t.string "ancestry"
|
52
|
+
t.string "description"
|
53
|
+
t.integer "position"
|
54
|
+
t.datetime "created_at", :null => false
|
55
|
+
t.datetime "updated_at", :null => false
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
File without changes
|
File without changes
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The page you were looking for doesn't exist (404)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/404.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>The page you were looking for doesn't exist.</h1>
|
23
|
+
<p>You may have mistyped the address or the page may have moved.</p>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The change you wanted was rejected (422)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/422.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>The change you wanted was rejected.</h1>
|
23
|
+
<p>Maybe you tried to change something you didn't have access to.</p>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/500.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>We're sorry, but something went wrong.</h1>
|
23
|
+
</div>
|
24
|
+
</body>
|
25
|
+
</html>
|
File without changes
|
@@ -0,0 +1,6 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
|
3
|
+
|
4
|
+
APP_PATH = File.expand_path('../../config/application', __FILE__)
|
5
|
+
require File.expand_path('../../config/boot', __FILE__)
|
6
|
+
require 'rails/commands'
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
RSpec.describe "ActiveAdmin::SortableTree", type: :feature do
|
4
|
+
context "configured as sortable" do
|
5
|
+
it "sorts by dragging vertically", js: true do
|
6
|
+
bottom = Category.create! name: "bottom", position: 0
|
7
|
+
top = Category.create! name: "top", position: 1
|
8
|
+
middle = Category.create! name: "middle", position: 2
|
9
|
+
|
10
|
+
visit admin_categories_path
|
11
|
+
|
12
|
+
expect(all(".ui-sortable li h3").map(&:text)).to eq(["bottom", "top", "middle"])
|
13
|
+
|
14
|
+
wait_for_ajax { drag_element("#category_#{middle.id} h3", dy: -100) }
|
15
|
+
wait_for_ajax { drag_element("#category_#{top.id} h3", dy: -100) }
|
16
|
+
|
17
|
+
expect(all(".ui-sortable li h3").map(&:text)).to eq(["top", "middle", "bottom"])
|
18
|
+
expect(Category.order(:position).map(&:name)).to eq(["top", "middle", "bottom"])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "configured as sortable tree" do
|
23
|
+
it "sorts by dragging vertically", js: true do
|
24
|
+
bottom = Category.create! name: "bottom", position: 0
|
25
|
+
top = Category.create! name: "top", position: 1
|
26
|
+
middle = Category.create! name: "middle", position: 2
|
27
|
+
|
28
|
+
visit admin_category_trees_path
|
29
|
+
|
30
|
+
expect(all(".ui-sortable li h3").map(&:text)).to eq(["bottom", "top", "middle"])
|
31
|
+
|
32
|
+
wait_for_ajax { drag_element("#category_tree_#{middle.id} h3", dy: -100) }
|
33
|
+
wait_for_ajax { drag_element("#category_tree_#{top.id} h3", dy: -100) }
|
34
|
+
|
35
|
+
expect(all(".ui-sortable li h3").map(&:text)).to eq(["top", "middle", "bottom"])
|
36
|
+
expect(Category.order(:position).map(&:name)).to eq(["top", "middle", "bottom"])
|
37
|
+
end
|
38
|
+
|
39
|
+
it "assigns hierarchy by dragging horizontally", js: true do
|
40
|
+
top = Category.create! name: "top", position: 0
|
41
|
+
middle = Category.create! name: "middle", position: 1
|
42
|
+
bottom = Category.create! name: "bottom", position: 2
|
43
|
+
expect(top.children).not_to include(middle)
|
44
|
+
|
45
|
+
visit admin_category_trees_path
|
46
|
+
|
47
|
+
wait_for_ajax { drag_element("#category_tree_#{middle.id} h3", dx: 40) }
|
48
|
+
wait_for_ajax { drag_element("#category_tree_#{bottom.id} h3", dx: 40) }
|
49
|
+
|
50
|
+
expect(top.children).to include(middle, bottom)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def drag_element(selector, options)
|
55
|
+
options.reverse_merge! moves: 20
|
56
|
+
page.execute_script(%Q($("#{selector}").simulate("drag", #{options.to_json} )))
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
2
|
+
ENV["RAILS_ENV"] ||= 'test'
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
require File.expand_path('../dummy/config/environment', __FILE__)
|
6
|
+
|
7
|
+
require 'rspec/rails'
|
8
|
+
require 'capybara/rails'
|
9
|
+
require 'phantomjs/poltergeist'
|
10
|
+
require 'database_cleaner'
|
11
|
+
|
12
|
+
Capybara.javascript_driver = :poltergeist
|
13
|
+
|
14
|
+
def reload_menus!
|
15
|
+
ActiveAdmin.application.namespaces.values.each{|n| n.reset_menu! }
|
16
|
+
end
|
17
|
+
|
18
|
+
def reload_routes!
|
19
|
+
Rails.application.reload_routes!
|
20
|
+
end
|
21
|
+
|
22
|
+
# Setup ActiveAdmin
|
23
|
+
ActiveAdmin.application.load_paths = [File.expand_path("../dummy/app/admin", __FILE__)]
|
24
|
+
ActiveAdmin.unload!
|
25
|
+
ActiveAdmin.load!
|
26
|
+
reload_menus!
|
27
|
+
reload_routes!
|
28
|
+
|
29
|
+
# Disabling authentication in specs so that we don't have to worry about
|
30
|
+
# it allover the place
|
31
|
+
ActiveAdmin.application.authentication_method = false
|
32
|
+
ActiveAdmin.application.current_user_method = false
|
33
|
+
|
34
|
+
Dir[File.expand_path("../support/**/*.rb", __FILE__)].each { |f| require f }
|
35
|
+
|
36
|
+
RSpec.configure do |config|
|
37
|
+
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
|
38
|
+
config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
39
|
+
|
40
|
+
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
41
|
+
# examples within a transaction, remove the following line or assign false
|
42
|
+
# instead of true.
|
43
|
+
config.use_transactional_fixtures = false
|
44
|
+
|
45
|
+
config.before(:each) do
|
46
|
+
DatabaseCleaner.strategy = :transaction
|
47
|
+
end
|
48
|
+
|
49
|
+
config.before(:each, js: true) do
|
50
|
+
DatabaseCleaner.strategy = :truncation
|
51
|
+
end
|
52
|
+
|
53
|
+
config.before(:each) do
|
54
|
+
DatabaseCleaner.start
|
55
|
+
end
|
56
|
+
|
57
|
+
config.after(:each) do
|
58
|
+
DatabaseCleaner.clean
|
59
|
+
end
|
60
|
+
|
61
|
+
config.infer_spec_type_from_file_location!
|
62
|
+
|
63
|
+
config.include Devise::TestHelpers, type: :controller
|
64
|
+
config.include WaitForAjax, type: :feature
|
65
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
RSpec.configure do |config|
|
2
|
+
# These two settings work together to allow you to limit a spec run
|
3
|
+
# to individual examples or groups you care about by tagging them with
|
4
|
+
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
5
|
+
# get run.
|
6
|
+
config.filter_run :focus
|
7
|
+
config.run_all_when_everything_filtered = true
|
8
|
+
|
9
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
10
|
+
# file, and it's useful to allow more verbose output when running an
|
11
|
+
# individual spec file.
|
12
|
+
if config.files_to_run.one?
|
13
|
+
# Use the documentation formatter for detailed output,
|
14
|
+
# unless a formatter has already been configured
|
15
|
+
# (e.g. via a command-line flag).
|
16
|
+
config.default_formatter = 'doc'
|
17
|
+
end
|
18
|
+
|
19
|
+
# Print the 10 slowest examples and example groups at the
|
20
|
+
# end of the spec run, to help surface which specs are running
|
21
|
+
# particularly slow.
|
22
|
+
config.profile_examples = 10
|
23
|
+
|
24
|
+
# Run specs in random order to surface order dependencies. If you find an
|
25
|
+
# order dependency and want to debug it, you can fix the order by providing
|
26
|
+
# the seed, which is printed after each run.
|
27
|
+
# --seed 1234
|
28
|
+
config.order = :random
|
29
|
+
|
30
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
31
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
32
|
+
# test failures related to randomization by passing the same `--seed` value
|
33
|
+
# as the one that triggered the failure.
|
34
|
+
Kernel.srand config.seed
|
35
|
+
|
36
|
+
# rspec-expectations config goes here. You can use an alternate
|
37
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
38
|
+
# assertions if you prefer.
|
39
|
+
config.expect_with :rspec do |expectations|
|
40
|
+
# Enable only the newer, non-monkey-patching expect syntax.
|
41
|
+
# For more details, see:
|
42
|
+
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
|
43
|
+
expectations.syntax = :expect
|
44
|
+
end
|
45
|
+
|
46
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
47
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
48
|
+
config.mock_with :rspec do |mocks|
|
49
|
+
# Enable only the newer, non-monkey-patching expect syntax.
|
50
|
+
# For more details, see:
|
51
|
+
# - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
52
|
+
mocks.syntax = :expect
|
53
|
+
|
54
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
55
|
+
# a real object. This is generally recommended.
|
56
|
+
mocks.verify_partial_doubles = true
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module WaitForAjax
|
2
|
+
def wait_for_ajax(count = 1)
|
3
|
+
page.execute_script 'window._ajaxCalls = 0, window._ajaxCompleteCounter = function() { window._ajaxCalls += 1; }'
|
4
|
+
page.execute_script '$(document).on("ajaxComplete", window._ajaxCompleteCounter)'
|
5
|
+
|
6
|
+
yield
|
7
|
+
|
8
|
+
Timeout.timeout(Capybara.default_wait_time) do
|
9
|
+
loop until finished_all_ajax_requests?(count)
|
10
|
+
end
|
11
|
+
|
12
|
+
page.execute_script '$(document).off("ajaxComplete", window._ajaxCompleteCounter)'
|
13
|
+
page.execute_script 'delete window._ajaxCompleteCounter, window._ajaxCalls'
|
14
|
+
end
|
15
|
+
|
16
|
+
def finished_all_ajax_requests?(count)
|
17
|
+
page.evaluate_script('window._ajaxCalls') == count
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,613 @@
|
|
1
|
+
/*
|
2
|
+
* jQuery UI Nested Sortable
|
3
|
+
* v 2.0 / 29 oct 2012
|
4
|
+
* http://mjsarfatti.com/sandbox/nestedSortable
|
5
|
+
*
|
6
|
+
* Depends on:
|
7
|
+
* jquery.ui.sortable.js 1.10+
|
8
|
+
*
|
9
|
+
* Copyright (c) 2010-2013 Manuele J Sarfatti
|
10
|
+
* Licensed under the MIT License
|
11
|
+
* http://www.opensource.org/licenses/mit-license.php
|
12
|
+
*/
|
13
|
+
|
14
|
+
(function($) {
|
15
|
+
|
16
|
+
function isOverAxis( x, reference, size ) {
|
17
|
+
return ( x > reference ) && ( x < ( reference + size ) );
|
18
|
+
}
|
19
|
+
|
20
|
+
$.widget("mjs.nestedSortable", $.extend({}, $.ui.sortable.prototype, {
|
21
|
+
|
22
|
+
options: {
|
23
|
+
doNotClear: false,
|
24
|
+
expandOnHover: 700,
|
25
|
+
isAllowed: function(placeholder, placeholderParent, originalItem) { return true; },
|
26
|
+
isTree: false,
|
27
|
+
listType: 'ol',
|
28
|
+
maxLevels: 0,
|
29
|
+
protectRoot: false,
|
30
|
+
rootID: null,
|
31
|
+
rtl: false,
|
32
|
+
startCollapsed: false,
|
33
|
+
tabSize: 20,
|
34
|
+
|
35
|
+
branchClass: 'mjs-nestedSortable-branch',
|
36
|
+
collapsedClass: 'mjs-nestedSortable-collapsed',
|
37
|
+
disableNestingClass: 'mjs-nestedSortable-no-nesting',
|
38
|
+
errorClass: 'mjs-nestedSortable-error',
|
39
|
+
expandedClass: 'mjs-nestedSortable-expanded',
|
40
|
+
hoveringClass: 'mjs-nestedSortable-hovering',
|
41
|
+
leafClass: 'mjs-nestedSortable-leaf'
|
42
|
+
},
|
43
|
+
|
44
|
+
_create: function() {
|
45
|
+
this.element.data('ui-sortable', this.element.data('mjs-nestedSortable'));
|
46
|
+
|
47
|
+
// mjs - prevent browser from freezing if the HTML is not correct
|
48
|
+
if (!this.element.is(this.options.listType))
|
49
|
+
throw new Error('nestedSortable: Please check that the listType option is set to your actual list type');
|
50
|
+
|
51
|
+
// mjs - force 'intersect' tolerance method if we have a tree with expanding/collapsing functionality
|
52
|
+
if (this.options.isTree && this.options.expandOnHover) {
|
53
|
+
this.options.tolerance = 'intersect';
|
54
|
+
}
|
55
|
+
|
56
|
+
$.ui.sortable.prototype._create.apply(this, arguments);
|
57
|
+
|
58
|
+
// mjs - prepare the tree by applying the right classes (the CSS is responsible for actual hide/show functionality)
|
59
|
+
if (this.options.isTree) {
|
60
|
+
var self = this;
|
61
|
+
$(this.items).each(function() {
|
62
|
+
var $li = this.item;
|
63
|
+
if ($li.children(self.options.listType).length) {
|
64
|
+
$li.addClass(self.options.branchClass);
|
65
|
+
// expand/collapse class only if they have children
|
66
|
+
if (self.options.startCollapsed) $li.addClass(self.options.collapsedClass);
|
67
|
+
else $li.addClass(self.options.expandedClass);
|
68
|
+
} else {
|
69
|
+
$li.addClass(self.options.leafClass);
|
70
|
+
}
|
71
|
+
})
|
72
|
+
}
|
73
|
+
},
|
74
|
+
|
75
|
+
_destroy: function() {
|
76
|
+
this.element
|
77
|
+
.removeData("mjs-nestedSortable")
|
78
|
+
.removeData("ui-sortable");
|
79
|
+
return $.ui.sortable.prototype._destroy.apply(this, arguments);
|
80
|
+
},
|
81
|
+
|
82
|
+
_mouseDrag: function(event) {
|
83
|
+
var i, item, itemElement, intersection,
|
84
|
+
o = this.options,
|
85
|
+
scrolled = false;
|
86
|
+
|
87
|
+
//Compute the helpers position
|
88
|
+
this.position = this._generatePosition(event);
|
89
|
+
this.positionAbs = this._convertPositionTo("absolute");
|
90
|
+
|
91
|
+
if (!this.lastPositionAbs) {
|
92
|
+
this.lastPositionAbs = this.positionAbs;
|
93
|
+
}
|
94
|
+
|
95
|
+
//Do scrolling
|
96
|
+
if(this.options.scroll) {
|
97
|
+
if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
|
98
|
+
|
99
|
+
if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
|
100
|
+
this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
|
101
|
+
} else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) {
|
102
|
+
this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
|
103
|
+
}
|
104
|
+
|
105
|
+
if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
|
106
|
+
this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
|
107
|
+
} else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) {
|
108
|
+
this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
|
109
|
+
}
|
110
|
+
|
111
|
+
} else {
|
112
|
+
|
113
|
+
if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
|
114
|
+
scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
|
115
|
+
} else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
|
116
|
+
scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
|
117
|
+
}
|
118
|
+
|
119
|
+
if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
|
120
|
+
scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
|
121
|
+
} else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
|
122
|
+
scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
|
123
|
+
}
|
124
|
+
|
125
|
+
}
|
126
|
+
|
127
|
+
if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
|
128
|
+
$.ui.ddmanager.prepareOffsets(this, event);
|
129
|
+
}
|
130
|
+
|
131
|
+
//Regenerate the absolute position used for position checks
|
132
|
+
this.positionAbs = this._convertPositionTo("absolute");
|
133
|
+
|
134
|
+
// mjs - find the top offset before rearrangement,
|
135
|
+
var previousTopOffset = this.placeholder.offset().top;
|
136
|
+
|
137
|
+
//Set the helper position
|
138
|
+
if(!this.options.axis || this.options.axis !== "y") {
|
139
|
+
this.helper[0].style.left = this.position.left+"px";
|
140
|
+
}
|
141
|
+
if(!this.options.axis || this.options.axis !== "x") {
|
142
|
+
this.helper[0].style.top = this.position.top+"px";
|
143
|
+
}
|
144
|
+
|
145
|
+
// mjs - check and reset hovering state at each cycle
|
146
|
+
this.hovering = this.hovering ? this.hovering : null;
|
147
|
+
this.mouseentered = this.mouseentered ? this.mouseentered : false;
|
148
|
+
|
149
|
+
// mjs - let's start caching some variables
|
150
|
+
var parentItem = (this.placeholder[0].parentNode.parentNode &&
|
151
|
+
$(this.placeholder[0].parentNode.parentNode).closest('.ui-sortable').length)
|
152
|
+
? $(this.placeholder[0].parentNode.parentNode)
|
153
|
+
: null,
|
154
|
+
level = this._getLevel(this.placeholder),
|
155
|
+
childLevels = this._getChildLevels(this.helper);
|
156
|
+
|
157
|
+
var newList = document.createElement(o.listType);
|
158
|
+
|
159
|
+
//Rearrange
|
160
|
+
for (i = this.items.length - 1; i >= 0; i--) {
|
161
|
+
|
162
|
+
//Cache variables and intersection, continue if no intersection
|
163
|
+
item = this.items[i];
|
164
|
+
itemElement = item.item[0];
|
165
|
+
intersection = this._intersectsWithPointer(item);
|
166
|
+
if (!intersection) {
|
167
|
+
continue;
|
168
|
+
}
|
169
|
+
|
170
|
+
// Only put the placeholder inside the current Container, skip all
|
171
|
+
// items form other containers. This works because when moving
|
172
|
+
// an item from one container to another the
|
173
|
+
// currentContainer is switched before the placeholder is moved.
|
174
|
+
//
|
175
|
+
// Without this moving items in "sub-sortables" can cause the placeholder to jitter
|
176
|
+
// beetween the outer and inner container.
|
177
|
+
if (item.instance !== this.currentContainer) {
|
178
|
+
continue;
|
179
|
+
}
|
180
|
+
|
181
|
+
// cannot intersect with itself
|
182
|
+
// no useless actions that have been done before
|
183
|
+
// no action if the item moved is the parent of the item checked
|
184
|
+
if (itemElement !== this.currentItem[0] &&
|
185
|
+
this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement &&
|
186
|
+
!$.contains(this.placeholder[0], itemElement) &&
|
187
|
+
(this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true)
|
188
|
+
) {
|
189
|
+
|
190
|
+
// mjs - we are intersecting an element: trigger the mouseenter event and store this state
|
191
|
+
if (!this.mouseentered) {
|
192
|
+
$(itemElement).mouseenter();
|
193
|
+
this.mouseentered = true;
|
194
|
+
}
|
195
|
+
|
196
|
+
// mjs - if the element has children and they are hidden, show them after a delay (CSS responsible)
|
197
|
+
if (o.isTree && $(itemElement).hasClass(o.collapsedClass) && o.expandOnHover) {
|
198
|
+
if (!this.hovering) {
|
199
|
+
$(itemElement).addClass(o.hoveringClass);
|
200
|
+
var self = this;
|
201
|
+
this.hovering = window.setTimeout(function() {
|
202
|
+
$(itemElement).removeClass(o.collapsedClass).addClass(o.expandedClass);
|
203
|
+
self.refreshPositions();
|
204
|
+
self._trigger("expand", event, self._uiHash());
|
205
|
+
}, o.expandOnHover);
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
this.direction = intersection == 1 ? "down" : "up";
|
210
|
+
|
211
|
+
// mjs - rearrange the elements and reset timeouts and hovering state
|
212
|
+
if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
|
213
|
+
$(itemElement).mouseleave();
|
214
|
+
this.mouseentered = false;
|
215
|
+
$(itemElement).removeClass(o.hoveringClass);
|
216
|
+
this.hovering && window.clearTimeout(this.hovering);
|
217
|
+
this.hovering = null;
|
218
|
+
|
219
|
+
// mjs - do not switch container if it's a root item and 'protectRoot' is true
|
220
|
+
// or if it's not a root item but we are trying to make it root
|
221
|
+
if (o.protectRoot
|
222
|
+
&& ! (this.currentItem[0].parentNode == this.element[0] // it's a root item
|
223
|
+
&& itemElement.parentNode != this.element[0]) // it's intersecting a non-root item
|
224
|
+
) {
|
225
|
+
if (this.currentItem[0].parentNode != this.element[0]
|
226
|
+
&& itemElement.parentNode == this.element[0]
|
227
|
+
) {
|
228
|
+
|
229
|
+
if ( ! $(itemElement).children(o.listType).length) {
|
230
|
+
itemElement.appendChild(newList);
|
231
|
+
o.isTree && $(itemElement).removeClass(o.leafClass).addClass(o.branchClass + ' ' + o.expandedClass);
|
232
|
+
}
|
233
|
+
|
234
|
+
var a = this.direction === "down" ? $(itemElement).prev().children(o.listType) : $(itemElement).children(o.listType);
|
235
|
+
if (a[0] !== undefined) {
|
236
|
+
this._rearrange(event, null, a);
|
237
|
+
}
|
238
|
+
|
239
|
+
} else {
|
240
|
+
this._rearrange(event, item);
|
241
|
+
}
|
242
|
+
} else if ( ! o.protectRoot) {
|
243
|
+
this._rearrange(event, item);
|
244
|
+
}
|
245
|
+
} else {
|
246
|
+
break;
|
247
|
+
}
|
248
|
+
|
249
|
+
// Clear emtpy ul's/ol's
|
250
|
+
this._clearEmpty(itemElement);
|
251
|
+
|
252
|
+
this._trigger("change", event, this._uiHash());
|
253
|
+
break;
|
254
|
+
}
|
255
|
+
}
|
256
|
+
|
257
|
+
// mjs - to find the previous sibling in the list, keep backtracking until we hit a valid list item.
|
258
|
+
var previousItem = this.placeholder[0].previousSibling ? $(this.placeholder[0].previousSibling) : null;
|
259
|
+
if (previousItem != null) {
|
260
|
+
while (previousItem[0].nodeName.toLowerCase() != 'li' || previousItem[0] == this.currentItem[0] || previousItem[0] == this.helper[0]) {
|
261
|
+
if (previousItem[0].previousSibling) {
|
262
|
+
previousItem = $(previousItem[0].previousSibling);
|
263
|
+
} else {
|
264
|
+
previousItem = null;
|
265
|
+
break;
|
266
|
+
}
|
267
|
+
}
|
268
|
+
}
|
269
|
+
|
270
|
+
// mjs - to find the next sibling in the list, keep stepping forward until we hit a valid list item.
|
271
|
+
var nextItem = this.placeholder[0].nextSibling ? $(this.placeholder[0].nextSibling) : null;
|
272
|
+
if (nextItem != null) {
|
273
|
+
while (nextItem[0].nodeName.toLowerCase() != 'li' || nextItem[0] == this.currentItem[0] || nextItem[0] == this.helper[0]) {
|
274
|
+
if (nextItem[0].nextSibling) {
|
275
|
+
nextItem = $(nextItem[0].nextSibling);
|
276
|
+
} else {
|
277
|
+
nextItem = null;
|
278
|
+
break;
|
279
|
+
}
|
280
|
+
}
|
281
|
+
}
|
282
|
+
|
283
|
+
this.beyondMaxLevels = 0;
|
284
|
+
|
285
|
+
// mjs - if the item is moved to the left, send it one level up but only if it's at the bottom of the list
|
286
|
+
if (parentItem != null
|
287
|
+
&& nextItem == null
|
288
|
+
&& ! (o.protectRoot && parentItem[0].parentNode == this.element[0])
|
289
|
+
&&
|
290
|
+
(o.rtl && (this.positionAbs.left + this.helper.outerWidth() > parentItem.offset().left + parentItem.outerWidth())
|
291
|
+
|| ! o.rtl && (this.positionAbs.left < parentItem.offset().left))
|
292
|
+
) {
|
293
|
+
|
294
|
+
parentItem.after(this.placeholder[0]);
|
295
|
+
if (o.isTree && parentItem.children(o.listItem).children('li:visible:not(.ui-sortable-helper)').length < 1) {
|
296
|
+
parentItem.removeClass(this.options.branchClass + ' ' + this.options.expandedClass)
|
297
|
+
.addClass(this.options.leafClass);
|
298
|
+
}
|
299
|
+
this._clearEmpty(parentItem[0]);
|
300
|
+
this._trigger("change", event, this._uiHash());
|
301
|
+
}
|
302
|
+
// mjs - if the item is below a sibling and is moved to the right, make it a child of that sibling
|
303
|
+
else if (previousItem != null
|
304
|
+
&& ! previousItem.hasClass(o.disableNestingClass)
|
305
|
+
&&
|
306
|
+
(previousItem.children(o.listType).length && previousItem.children(o.listType).is(':visible')
|
307
|
+
|| ! previousItem.children(o.listType).length)
|
308
|
+
&& ! (o.protectRoot && this.currentItem[0].parentNode == this.element[0])
|
309
|
+
&&
|
310
|
+
(o.rtl && (this.positionAbs.left + this.helper.outerWidth() < previousItem.offset().left + previousItem.outerWidth() - o.tabSize)
|
311
|
+
|| ! o.rtl && (this.positionAbs.left > previousItem.offset().left + o.tabSize))
|
312
|
+
) {
|
313
|
+
|
314
|
+
this._isAllowed(previousItem, level, level+childLevels+1);
|
315
|
+
|
316
|
+
if (!previousItem.children(o.listType).length) {
|
317
|
+
previousItem[0].appendChild(newList);
|
318
|
+
o.isTree && previousItem.removeClass(o.leafClass).addClass(o.branchClass + ' ' + o.expandedClass);
|
319
|
+
}
|
320
|
+
|
321
|
+
// mjs - if this item is being moved from the top, add it to the top of the list.
|
322
|
+
if (previousTopOffset && (previousTopOffset <= previousItem.offset().top)) {
|
323
|
+
previousItem.children(o.listType).prepend(this.placeholder);
|
324
|
+
}
|
325
|
+
// mjs - otherwise, add it to the bottom of the list.
|
326
|
+
else {
|
327
|
+
previousItem.children(o.listType)[0].appendChild(this.placeholder[0]);
|
328
|
+
}
|
329
|
+
|
330
|
+
this._trigger("change", event, this._uiHash());
|
331
|
+
}
|
332
|
+
else {
|
333
|
+
this._isAllowed(parentItem, level, level+childLevels);
|
334
|
+
}
|
335
|
+
|
336
|
+
//Post events to containers
|
337
|
+
this._contactContainers(event);
|
338
|
+
|
339
|
+
//Interconnect with droppables
|
340
|
+
if($.ui.ddmanager) {
|
341
|
+
$.ui.ddmanager.drag(this, event);
|
342
|
+
}
|
343
|
+
|
344
|
+
//Call callbacks
|
345
|
+
this._trigger('sort', event, this._uiHash());
|
346
|
+
|
347
|
+
this.lastPositionAbs = this.positionAbs;
|
348
|
+
return false;
|
349
|
+
|
350
|
+
},
|
351
|
+
|
352
|
+
_mouseStop: function(event, noPropagation) {
|
353
|
+
|
354
|
+
// mjs - if the item is in a position not allowed, send it back
|
355
|
+
if (this.beyondMaxLevels) {
|
356
|
+
|
357
|
+
this.placeholder.removeClass(this.options.errorClass);
|
358
|
+
|
359
|
+
if (this.domPosition.prev) {
|
360
|
+
$(this.domPosition.prev).after(this.placeholder);
|
361
|
+
} else {
|
362
|
+
$(this.domPosition.parent).prepend(this.placeholder);
|
363
|
+
}
|
364
|
+
|
365
|
+
this._trigger("revert", event, this._uiHash());
|
366
|
+
|
367
|
+
}
|
368
|
+
|
369
|
+
|
370
|
+
// mjs - clear the hovering timeout, just to be sure
|
371
|
+
$('.'+this.options.hoveringClass).mouseleave().removeClass(this.options.hoveringClass);
|
372
|
+
this.mouseentered = false;
|
373
|
+
this.hovering && window.clearTimeout(this.hovering);
|
374
|
+
this.hovering = null;
|
375
|
+
|
376
|
+
$.ui.sortable.prototype._mouseStop.apply(this, arguments);
|
377
|
+
|
378
|
+
},
|
379
|
+
|
380
|
+
// mjs - this function is slightly modified to make it easier to hover over a collapsed element and have it expand
|
381
|
+
_intersectsWithSides: function(item) {
|
382
|
+
|
383
|
+
var half = this.options.isTree ? .8 : .5;
|
384
|
+
|
385
|
+
var isOverBottomHalf = isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height*half), item.height),
|
386
|
+
isOverTopHalf = isOverAxis(this.positionAbs.top + this.offset.click.top, item.top - (item.height*half), item.height),
|
387
|
+
isOverRightHalf = isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
|
388
|
+
verticalDirection = this._getDragVerticalDirection(),
|
389
|
+
horizontalDirection = this._getDragHorizontalDirection();
|
390
|
+
|
391
|
+
if (this.floating && horizontalDirection) {
|
392
|
+
return ((horizontalDirection == "right" && isOverRightHalf) || (horizontalDirection == "left" && !isOverRightHalf));
|
393
|
+
} else {
|
394
|
+
return verticalDirection && ((verticalDirection == "down" && isOverBottomHalf) || (verticalDirection == "up" && isOverTopHalf));
|
395
|
+
}
|
396
|
+
|
397
|
+
},
|
398
|
+
|
399
|
+
_contactContainers: function(event) {
|
400
|
+
|
401
|
+
if (this.options.protectRoot && this.currentItem[0].parentNode == this.element[0] ) {
|
402
|
+
return;
|
403
|
+
}
|
404
|
+
|
405
|
+
$.ui.sortable.prototype._contactContainers.apply(this, arguments);
|
406
|
+
|
407
|
+
},
|
408
|
+
|
409
|
+
_clear: function(event, noPropagation) {
|
410
|
+
|
411
|
+
$.ui.sortable.prototype._clear.apply(this, arguments);
|
412
|
+
|
413
|
+
// mjs - clean last empty ul/ol
|
414
|
+
for (var i = this.items.length - 1; i >= 0; i--) {
|
415
|
+
var item = this.items[i].item[0];
|
416
|
+
this._clearEmpty(item);
|
417
|
+
}
|
418
|
+
|
419
|
+
},
|
420
|
+
|
421
|
+
serialize: function(options) {
|
422
|
+
|
423
|
+
var o = $.extend({}, this.options, options),
|
424
|
+
items = this._getItemsAsjQuery(o && o.connected),
|
425
|
+
str = [];
|
426
|
+
|
427
|
+
$(items).each(function() {
|
428
|
+
var res = ($(o.item || this).attr(o.attribute || 'id') || '')
|
429
|
+
.match(o.expression || (/(.+)[-=_](.+)/)),
|
430
|
+
pid = ($(o.item || this).parent(o.listType)
|
431
|
+
.parent(o.items)
|
432
|
+
.attr(o.attribute || 'id') || '')
|
433
|
+
.match(o.expression || (/(.+)[-=_](.+)/));
|
434
|
+
|
435
|
+
if (res) {
|
436
|
+
str.push(((o.key || res[1]) + '[' + (o.key && o.expression ? res[1] : res[2]) + ']')
|
437
|
+
+ '='
|
438
|
+
+ (pid ? (o.key && o.expression ? pid[1] : pid[2]) : o.rootID));
|
439
|
+
}
|
440
|
+
});
|
441
|
+
|
442
|
+
if(!str.length && o.key) {
|
443
|
+
str.push(o.key + '=');
|
444
|
+
}
|
445
|
+
|
446
|
+
return str.join('&');
|
447
|
+
|
448
|
+
},
|
449
|
+
|
450
|
+
toHierarchy: function(options) {
|
451
|
+
|
452
|
+
var o = $.extend({}, this.options, options),
|
453
|
+
sDepth = o.startDepthCount || 0,
|
454
|
+
ret = [];
|
455
|
+
|
456
|
+
$(this.element).children(o.items).each(function () {
|
457
|
+
var level = _recursiveItems(this);
|
458
|
+
ret.push(level);
|
459
|
+
});
|
460
|
+
|
461
|
+
return ret;
|
462
|
+
|
463
|
+
function _recursiveItems(item) {
|
464
|
+
var id = ($(item).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
|
465
|
+
if (id) {
|
466
|
+
var currentItem = {"id" : id[2]};
|
467
|
+
if ($(item).children(o.listType).children(o.items).length > 0) {
|
468
|
+
currentItem.children = [];
|
469
|
+
$(item).children(o.listType).children(o.items).each(function() {
|
470
|
+
var level = _recursiveItems(this);
|
471
|
+
currentItem.children.push(level);
|
472
|
+
});
|
473
|
+
}
|
474
|
+
return currentItem;
|
475
|
+
}
|
476
|
+
}
|
477
|
+
},
|
478
|
+
|
479
|
+
toArray: function(options) {
|
480
|
+
|
481
|
+
var o = $.extend({}, this.options, options),
|
482
|
+
sDepth = o.startDepthCount || 0,
|
483
|
+
ret = [],
|
484
|
+
left = 1;
|
485
|
+
|
486
|
+
if (!o.excludeRoot) {
|
487
|
+
ret.push({
|
488
|
+
"item_id": o.rootID,
|
489
|
+
"parent_id": null,
|
490
|
+
"depth": sDepth,
|
491
|
+
"left": left,
|
492
|
+
"right": ($(o.items, this.element).length + 1) * 2
|
493
|
+
});
|
494
|
+
left++
|
495
|
+
}
|
496
|
+
|
497
|
+
$(this.element).children(o.items).each(function () {
|
498
|
+
left = _recursiveArray(this, sDepth + 1, left);
|
499
|
+
});
|
500
|
+
|
501
|
+
ret = ret.sort(function(a,b){ return (a.left - b.left); });
|
502
|
+
|
503
|
+
return ret;
|
504
|
+
|
505
|
+
function _recursiveArray(item, depth, left) {
|
506
|
+
|
507
|
+
var right = left + 1,
|
508
|
+
id,
|
509
|
+
pid;
|
510
|
+
|
511
|
+
if ($(item).children(o.listType).children(o.items).length > 0) {
|
512
|
+
depth ++;
|
513
|
+
$(item).children(o.listType).children(o.items).each(function () {
|
514
|
+
right = _recursiveArray($(this), depth, right);
|
515
|
+
});
|
516
|
+
depth --;
|
517
|
+
}
|
518
|
+
|
519
|
+
id = ($(item).attr(o.attribute || 'id')).match(o.expression || (/(.+)[-=_](.+)/));
|
520
|
+
|
521
|
+
if (depth === sDepth + 1) {
|
522
|
+
pid = o.rootID;
|
523
|
+
} else {
|
524
|
+
var parentItem = ($(item).parent(o.listType)
|
525
|
+
.parent(o.items)
|
526
|
+
.attr(o.attribute || 'id'))
|
527
|
+
.match(o.expression || (/(.+)[-=_](.+)/));
|
528
|
+
pid = parentItem[2];
|
529
|
+
}
|
530
|
+
|
531
|
+
if (id) {
|
532
|
+
ret.push({"item_id": id[2], "parent_id": pid, "depth": depth, "left": left, "right": right});
|
533
|
+
}
|
534
|
+
|
535
|
+
left = right + 1;
|
536
|
+
return left;
|
537
|
+
}
|
538
|
+
|
539
|
+
},
|
540
|
+
|
541
|
+
_clearEmpty: function(item) {
|
542
|
+
var o = this.options;
|
543
|
+
|
544
|
+
var emptyList = $(item).children(o.listType);
|
545
|
+
|
546
|
+
if (emptyList.length && !emptyList.children().length && !o.doNotClear) {
|
547
|
+
o.isTree && $(item).removeClass(o.branchClass + ' ' + o.expandedClass).addClass(o.leafClass);
|
548
|
+
emptyList.remove();
|
549
|
+
} else if (o.isTree && emptyList.length && emptyList.children().length && emptyList.is(':visible')) {
|
550
|
+
$(item).removeClass(o.leafClass).addClass(o.branchClass + ' ' + o.expandedClass);
|
551
|
+
} else if (o.isTree && emptyList.length && emptyList.children().length && !emptyList.is(':visible')) {
|
552
|
+
$(item).removeClass(o.leafClass).addClass(o.branchClass + ' ' + o.collapsedClass);
|
553
|
+
}
|
554
|
+
|
555
|
+
},
|
556
|
+
|
557
|
+
_getLevel: function(item) {
|
558
|
+
|
559
|
+
var level = 1;
|
560
|
+
|
561
|
+
if (this.options.listType) {
|
562
|
+
var list = item.closest(this.options.listType);
|
563
|
+
while (list && list.length > 0 &&
|
564
|
+
!list.is('.ui-sortable')) {
|
565
|
+
level++;
|
566
|
+
list = list.parent().closest(this.options.listType);
|
567
|
+
}
|
568
|
+
}
|
569
|
+
|
570
|
+
return level;
|
571
|
+
},
|
572
|
+
|
573
|
+
_getChildLevels: function(parent, depth) {
|
574
|
+
var self = this,
|
575
|
+
o = this.options,
|
576
|
+
result = 0;
|
577
|
+
depth = depth || 0;
|
578
|
+
|
579
|
+
$(parent).children(o.listType).children(o.items).each(function (index, child) {
|
580
|
+
result = Math.max(self._getChildLevels(child, depth + 1), result);
|
581
|
+
});
|
582
|
+
|
583
|
+
return depth ? result + 1 : result;
|
584
|
+
},
|
585
|
+
|
586
|
+
_isAllowed: function(parentItem, level, levels) {
|
587
|
+
var o = this.options,
|
588
|
+
maxLevels = this.placeholder.closest('.ui-sortable').nestedSortable('option', 'maxLevels'); // this takes into account the maxLevels set to the recipient list
|
589
|
+
|
590
|
+
// mjs - is the root protected?
|
591
|
+
// mjs - are we nesting too deep?
|
592
|
+
if ( ! o.isAllowed(this.placeholder, parentItem, this.currentItem)) {
|
593
|
+
this.placeholder.addClass(o.errorClass);
|
594
|
+
if (maxLevels < levels && maxLevels != 0) {
|
595
|
+
this.beyondMaxLevels = levels - maxLevels;
|
596
|
+
} else {
|
597
|
+
this.beyondMaxLevels = 1;
|
598
|
+
}
|
599
|
+
} else {
|
600
|
+
if (maxLevels < levels && maxLevels != 0) {
|
601
|
+
this.placeholder.addClass(o.errorClass);
|
602
|
+
this.beyondMaxLevels = levels - maxLevels;
|
603
|
+
} else {
|
604
|
+
this.placeholder.removeClass(o.errorClass);
|
605
|
+
this.beyondMaxLevels = 0;
|
606
|
+
}
|
607
|
+
}
|
608
|
+
}
|
609
|
+
|
610
|
+
}));
|
611
|
+
|
612
|
+
$.mjs.nestedSortable.prototype.options = $.extend({}, $.ui.sortable.prototype.options, $.mjs.nestedSortable.prototype.options);
|
613
|
+
})(jQuery);
|