├── .gitignore
├── Gemfile
├── Gemfile.lock
├── MIT-LICENSE
├── Rakefile
├── app
├── assets
│ ├── images
│ │ └── sortable_tree_rails
│ │ │ └── .keep
│ ├── javascripts
│ │ └── sortable_tree_rails
│ │ │ └── .keep
│ └── stylesheets
│ │ └── sortable_tree_rails
│ │ └── .keep
├── controllers
│ ├── .keep
│ └── sortable_tree_controller.rb
├── helpers
│ ├── .keep
│ └── sortable_tree_helper.rb
├── mailers
│ └── .keep
├── models
│ └── .keep
└── views
│ ├── .keep
│ └── sortable_tree
│ └── _sortable_tree.html.haml
├── bin
└── rails
├── config
└── routes.rb
├── lib
├── assets
│ ├── javascripts
│ │ ├── jquery.mjs.nestedSortable.js
│ │ └── sortable_tree.js
│ └── stylesheets
│ │ └── sortable_tree.scss
├── sortable_tree_rails.rb
├── sortable_tree_rails
│ ├── engine.rb
│ └── version.rb
└── tasks
│ └── sortable_tree_rails_tasks.rake
├── readme.md
├── sortable_tree_rails.gemspec
└── spec
└── dummy
├── Rakefile
├── app
├── assets
│ ├── images
│ │ └── .keep
│ ├── javascripts
│ │ └── application.js
│ └── stylesheets
│ │ └── application.scss
├── controllers
│ ├── application_controller.rb
│ ├── categories_controller.rb
│ └── concerns
│ │ └── .keep
├── helpers
│ └── application_helper.rb
├── mailers
│ └── .keep
├── models
│ ├── .keep
│ ├── category.rb
│ └── concerns
│ │ └── .keep
└── views
│ ├── categories
│ ├── _controls.html.haml
│ ├── index.html.haml
│ └── manage.html.haml
│ └── layouts
│ └── application.html.erb
├── bin
├── bundle
├── rails
├── rake
└── setup
├── config.ru
├── config
├── application.rb
├── boot.rb
├── database.yml
├── environment.rb
├── environments
│ ├── development.rb
│ ├── production.rb
│ └── test.rb
├── initializers
│ ├── assets.rb
│ ├── backtrace_silencers.rb
│ ├── cookies_serializer.rb
│ ├── filter_parameter_logging.rb
│ ├── inflections.rb
│ ├── mime_types.rb
│ ├── session_store.rb
│ └── wrap_parameters.rb
├── locales
│ └── en.yml
├── routes.rb
└── secrets.yml
├── db
├── migrate
│ ├── 20160528172331_create_categories.rb
│ └── 20160528172947_add_ancestry_to_categories.rb
└── schema.rb
├── lib
└── assets
│ └── .keep
├── log
└── .keep
└── public
├── 404.html
├── 422.html
├── 500.html
└── favicon.ico
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .bundle/
3 | log/*.log
4 | pkg/
5 | spec/dummy/db/*.sqlite3
6 | spec/dummy/db/*.sqlite3-journal
7 | spec/dummy/log/*.log
8 | spec/dummy/tmp/
9 | spec/dummy/.sass-cache
10 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Declare your gem's dependencies in sortable_tree_rails.gemspec.
4 | # Bundler will treat runtime dependencies like base dependencies, and
5 | # development dependencies will be added by default to the :development group.
6 | gemspec
7 |
8 | ruby "2.4.2"
9 |
10 | gem 'rails', '5.1.3'
11 |
12 |
13 | gem 'haml-rails', '1.0.0'
14 | gem 'sass-rails', '~> 5.0.6'
15 | gem 'uglifier', '3.2.0'
16 | gem 'jquery-rails', '4.3.1'
17 | gem "jquery-ui-rails"
18 | gem 'font-awesome-rails', '~>4.7'
19 |
20 | gem 'ancestry'
21 |
22 | # Declare any dependencies that are still in development here instead of in
23 | # your gemspec. These might include edge Rails or gems from your path or
24 | # Git. Remember to move these dependencies to your gemspec before releasing
25 | # your gem to rubygems.org.
26 |
27 | # To use a debugger
28 | # gem 'byebug', group: [:development, :test]
29 |
30 | gem 'tzinfo-data' #, platforms: [:mingw, :mswin, :x64_mingw, :jruby]
31 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | sortable_tree_rails (0.0.8)
5 | rails (>= 4.2.7)
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | actioncable (5.1.3)
11 | actionpack (= 5.1.3)
12 | nio4r (~> 2.0)
13 | websocket-driver (~> 0.6.1)
14 | actionmailer (5.1.3)
15 | actionpack (= 5.1.3)
16 | actionview (= 5.1.3)
17 | activejob (= 5.1.3)
18 | mail (~> 2.5, >= 2.5.4)
19 | rails-dom-testing (~> 2.0)
20 | actionpack (5.1.3)
21 | actionview (= 5.1.3)
22 | activesupport (= 5.1.3)
23 | rack (~> 2.0)
24 | rack-test (~> 0.6.3)
25 | rails-dom-testing (~> 2.0)
26 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
27 | actionview (5.1.3)
28 | activesupport (= 5.1.3)
29 | builder (~> 3.1)
30 | erubi (~> 1.4)
31 | rails-dom-testing (~> 2.0)
32 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
33 | activejob (5.1.3)
34 | activesupport (= 5.1.3)
35 | globalid (>= 0.3.6)
36 | activemodel (5.1.3)
37 | activesupport (= 5.1.3)
38 | activerecord (5.1.3)
39 | activemodel (= 5.1.3)
40 | activesupport (= 5.1.3)
41 | arel (~> 8.0)
42 | activesupport (5.1.3)
43 | concurrent-ruby (~> 1.0, >= 1.0.2)
44 | i18n (~> 0.7)
45 | minitest (~> 5.1)
46 | tzinfo (~> 1.1)
47 | ancestry (3.0.1)
48 | activerecord (>= 3.2.0)
49 | arel (8.0.0)
50 | builder (3.2.3)
51 | concurrent-ruby (1.0.5)
52 | crass (1.0.3)
53 | erubi (1.7.0)
54 | erubis (2.7.0)
55 | execjs (2.7.0)
56 | ffi (1.9.21)
57 | ffi (1.9.21-x64-mingw32)
58 | font-awesome-rails (4.7.0.3)
59 | railties (>= 3.2, < 5.2)
60 | globalid (0.4.1)
61 | activesupport (>= 4.2.0)
62 | haml (5.0.4)
63 | temple (>= 0.8.0)
64 | tilt
65 | haml-rails (1.0.0)
66 | actionpack (>= 4.0.1)
67 | activesupport (>= 4.0.1)
68 | haml (>= 4.0.6, < 6.0)
69 | html2haml (>= 1.0.1)
70 | railties (>= 4.0.1)
71 | html2haml (2.2.0)
72 | erubis (~> 2.7.0)
73 | haml (>= 4.0, < 6)
74 | nokogiri (>= 1.6.0)
75 | ruby_parser (~> 3.5)
76 | i18n (0.9.5)
77 | concurrent-ruby (~> 1.0)
78 | jquery-rails (4.3.1)
79 | rails-dom-testing (>= 1, < 3)
80 | railties (>= 4.2.0)
81 | thor (>= 0.14, < 2.0)
82 | jquery-ui-rails (6.0.1)
83 | railties (>= 3.2.16)
84 | loofah (2.2.0)
85 | crass (~> 1.0.2)
86 | nokogiri (>= 1.5.9)
87 | mail (2.7.0)
88 | mini_mime (>= 0.1.1)
89 | method_source (0.9.0)
90 | mini_mime (1.0.0)
91 | mini_portile2 (2.3.0)
92 | minitest (5.11.3)
93 | nio4r (2.2.0)
94 | nokogiri (1.8.2)
95 | mini_portile2 (~> 2.3.0)
96 | nokogiri (1.8.2-x64-mingw32)
97 | mini_portile2 (~> 2.3.0)
98 | rack (2.0.4)
99 | rack-test (0.6.3)
100 | rack (>= 1.0)
101 | rails (5.1.3)
102 | actioncable (= 5.1.3)
103 | actionmailer (= 5.1.3)
104 | actionpack (= 5.1.3)
105 | actionview (= 5.1.3)
106 | activejob (= 5.1.3)
107 | activemodel (= 5.1.3)
108 | activerecord (= 5.1.3)
109 | activesupport (= 5.1.3)
110 | bundler (>= 1.3.0)
111 | railties (= 5.1.3)
112 | sprockets-rails (>= 2.0.0)
113 | rails-dom-testing (2.0.3)
114 | activesupport (>= 4.2.0)
115 | nokogiri (>= 1.6)
116 | rails-html-sanitizer (1.0.3)
117 | loofah (~> 2.0)
118 | railties (5.1.3)
119 | actionpack (= 5.1.3)
120 | activesupport (= 5.1.3)
121 | method_source
122 | rake (>= 0.8.7)
123 | thor (>= 0.18.1, < 2.0)
124 | rake (12.3.0)
125 | rb-fsevent (0.10.2)
126 | rb-inotify (0.9.10)
127 | ffi (>= 0.5.0, < 2)
128 | ruby_parser (3.11.0)
129 | sexp_processor (~> 4.9)
130 | sass (3.5.5)
131 | sass-listen (~> 4.0.0)
132 | sass-listen (4.0.0)
133 | rb-fsevent (~> 0.9, >= 0.9.4)
134 | rb-inotify (~> 0.9, >= 0.9.7)
135 | sass-rails (5.0.7)
136 | railties (>= 4.0.0, < 6)
137 | sass (~> 3.1)
138 | sprockets (>= 2.8, < 4.0)
139 | sprockets-rails (>= 2.0, < 4.0)
140 | tilt (>= 1.1, < 3)
141 | sexp_processor (4.10.1)
142 | sprockets (3.7.1)
143 | concurrent-ruby (~> 1.0)
144 | rack (> 1, < 3)
145 | sprockets-rails (3.2.1)
146 | actionpack (>= 4.0)
147 | activesupport (>= 4.0)
148 | sprockets (>= 3.0.0)
149 | sqlite3 (1.3.13)
150 | sqlite3 (1.3.13-x64-mingw32)
151 | temple (0.8.0)
152 | thor (0.20.0)
153 | thread_safe (0.3.6)
154 | tilt (2.0.8)
155 | tzinfo (1.2.5)
156 | thread_safe (~> 0.1)
157 | tzinfo-data (1.2018.3)
158 | tzinfo (>= 1.0.0)
159 | uglifier (3.2.0)
160 | execjs (>= 0.3.0, < 3)
161 | websocket-driver (0.6.5)
162 | websocket-extensions (>= 0.1.0)
163 | websocket-extensions (0.1.3)
164 |
165 | PLATFORMS
166 | ruby
167 | x64-mingw32
168 |
169 | DEPENDENCIES
170 | ancestry
171 | font-awesome-rails (~> 4.7)
172 | haml-rails (= 1.0.0)
173 | jquery-rails (= 4.3.1)
174 | jquery-ui-rails
175 | rails (= 5.1.3)
176 | sass-rails (~> 5.0.6)
177 | sortable_tree_rails!
178 | sqlite3
179 | tzinfo-data
180 | uglifier (= 3.2.0)
181 |
182 | RUBY VERSION
183 | ruby 2.4.2p198
184 |
185 | BUNDLED WITH
186 | 1.16.0
187 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2016 Max Ivak
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | begin
2 | require 'bundler/setup'
3 | rescue LoadError
4 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5 | end
6 |
7 | require 'rdoc/task'
8 |
9 | RDoc::Task.new(:rdoc) do |rdoc|
10 | rdoc.rdoc_dir = 'rdoc'
11 | rdoc.title = 'SortableTreeRails'
12 | rdoc.options << '--line-numbers'
13 | rdoc.rdoc_files.include('readme.md')
14 | rdoc.rdoc_files.include('lib/**/*.rb')
15 | end
16 |
17 | APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18 | load 'rails/tasks/engine.rake'
19 |
20 |
21 | load 'rails/tasks/statistics.rake'
22 |
23 |
24 |
25 | Bundler::GemHelper.install_tasks
26 |
27 |
--------------------------------------------------------------------------------
/app/assets/images/sortable_tree_rails/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxivak/sortable_tree_rails/949f1c8665bf7c393a191098e9cdf685ecba8a0f/app/assets/images/sortable_tree_rails/.keep
--------------------------------------------------------------------------------
/app/assets/javascripts/sortable_tree_rails/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxivak/sortable_tree_rails/949f1c8665bf7c393a191098e9cdf685ecba8a0f/app/assets/javascripts/sortable_tree_rails/.keep
--------------------------------------------------------------------------------
/app/assets/stylesheets/sortable_tree_rails/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxivak/sortable_tree_rails/949f1c8665bf7c393a191098e9cdf685ecba8a0f/app/assets/stylesheets/sortable_tree_rails/.keep
--------------------------------------------------------------------------------
/app/controllers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxivak/sortable_tree_rails/949f1c8665bf7c393a191098e9cdf685ecba8a0f/app/controllers/.keep
--------------------------------------------------------------------------------
/app/controllers/sortable_tree_controller.rb:
--------------------------------------------------------------------------------
1 | require 'active_support/concern'
2 |
3 | module SortableTreeController
4 | module Sort
5 | extend ActiveSupport::Concern
6 |
7 | included do
8 | helper SortableTreeHelper
9 | end
10 |
11 | module ClassMethods
12 |
13 | def sortable_tree(class_name, options = {})
14 | define_method("sort") do
15 | resource_class = class_name.to_s.camelize.constantize
16 |
17 | # options
18 | #options[:tree] = true
19 |
20 | sorting_attribute = options.fetch(:sorting_attribute, 'pos')
21 | parent_method = options.fetch(:parent_method, 'parent')
22 | #options[:sorting_attribute] ||= 'pos'
23 | #options[:parent_method] ||= 'parent'
24 |
25 | records = params[:cat].to_unsafe_h.inject({}) do |res, (resource, parent_resource)|
26 | res[resource_class.find(resource)] = resource_class.find(parent_resource) rescue nil
27 | res
28 | end
29 |
30 | errors = []
31 | ActiveRecord::Base.transaction do
32 | records.each_with_index do |(record, parent_record), position|
33 |
34 | if sorting_attribute
35 | record.send "#{sorting_attribute}=", position
36 | end
37 |
38 | if parent_method
39 | record.send "#{parent_method}=", parent_record
40 | end
41 |
42 | errors << {record.id => record.errors} if !record.save
43 | end
44 | end
45 |
46 | #
47 | if errors.empty?
48 | head 200
49 | else
50 | render json: errors, status: 422
51 | end
52 |
53 |
54 | end
55 | end
56 | end
57 |
58 |
59 |
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/app/helpers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxivak/sortable_tree_rails/949f1c8665bf7c393a191098e9cdf685ecba8a0f/app/helpers/.keep
--------------------------------------------------------------------------------
/app/helpers/sortable_tree_helper.rb:
--------------------------------------------------------------------------------
1 | module SortableTreeHelper
2 |
3 | def render_sortable_tree(items, opts={})
4 | render :partial=>'sortable_tree/sortable_tree', locals: {items: items, options: opts}
5 | end
6 |
7 |
8 | def sortable_tree_render_nested_groups(groups, opts={})
9 | content_tag(:ol) do
10 | groups.map do |item, sub_groups|
11 | content_tag(:li, {id: "cat_#{item.id}"} ) do
12 | #(item.title + sortable_tree_render_nested_groups(sub_groups, opts)).html_safe
13 |
14 | s = content_tag(:div, {class: 'item'}) do
15 | (
16 | #'
'\
17 | ''+sortable_tree_build_show(item, opts)+'
'\
18 | ''+sortable_tree_build_actions(item, opts)+'
'
19 | ).html_safe
20 | end
21 | (s + sortable_tree_render_nested_groups(sub_groups, opts)).html_safe
22 | end
23 |
24 | end.join.html_safe
25 | end
26 | end
27 |
28 | def sortable_tree_build_actions(item, opts={})
29 | partial = opts[:controls_partial] || nil
30 | if partial
31 | return render :partial=>partial, locals: {item: item, options: opts}
32 | else
33 | return ""
34 | end
35 | end
36 |
37 | def sortable_tree_build_show(item, opts={})
38 | partial = opts[:show_partial] || nil
39 | if partial
40 | return render :partial=>partial, locals: {item: item, options: opts}
41 | else
42 | title = item.send(opts[:name_method] || :name) || ''
43 | return title
44 | end
45 | end
46 |
47 | end
--------------------------------------------------------------------------------
/app/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxivak/sortable_tree_rails/949f1c8665bf7c393a191098e9cdf685ecba8a0f/app/mailers/.keep
--------------------------------------------------------------------------------
/app/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxivak/sortable_tree_rails/949f1c8665bf7c393a191098e9cdf685ecba8a0f/app/models/.keep
--------------------------------------------------------------------------------
/app/views/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxivak/sortable_tree_rails/949f1c8665bf7c393a191098e9cdf685ecba8a0f/app/views/.keep
--------------------------------------------------------------------------------
/app/views/sortable_tree/_sortable_tree.html.haml:
--------------------------------------------------------------------------------
1 | .sortable_tree_wrapper
2 | %ol.sortable_tree{data: {'sortable-url' => options[:sort_url], 'max-levels'=> (options[:max_levels] || 5)} }
3 | - items.each do |root, children|
4 | %li{id: "cat_#{root.id}"}
5 | .item
6 | /.cell.left
7 | / %i.handle
8 | %h3.cell.left
9 | =root.send(options[:name_method] || :name)
10 | .cell.right.controls
11 | - if options[:controls_partial]
12 | = render options[:controls_partial], item: root
13 |
14 | = sortable_tree_render_nested_groups(children, options.merge({list_tag: :ol, element_class: 'item'}))
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby.exe
2 | # This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
3 |
4 | ENGINE_ROOT = File.expand_path('../..', __FILE__)
5 | ENGINE_PATH = File.expand_path('../../lib/sortable_tree_rails/engine', __FILE__)
6 |
7 | # Set up gems listed in the Gemfile.
8 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
9 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
10 |
11 | require 'rails/all'
12 | require 'rails/engine/commands'
13 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | end
3 |
--------------------------------------------------------------------------------
/lib/assets/javascripts/jquery.mjs.nestedSortable.js:
--------------------------------------------------------------------------------
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 | }));
908 |
--------------------------------------------------------------------------------
/lib/assets/javascripts/sortable_tree.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function(){
2 |
3 | $('.sortable_tree').each(function() {
4 | $(this).nestedSortable({
5 | forceHelperSizeType: true,
6 | errorClass: 'cantdoit',
7 | disableNesting: 'cantdoit',
8 | handle: '> .item',
9 | helper: 'clone',
10 | listType: 'ol',
11 | items: 'li',
12 | opacity: 0.6,
13 | placeholder: 'placeholder',
14 | revert: 250,
15 | maxLevels: $(this).data('max-levels'),
16 | //maxLevels: #{options[:max_levels] || 5},
17 | //tabSize: 20,
18 | // protectRoot: $(this).data('protect-root'),
19 |
20 | // prevent drag flickers
21 | tolerance: 'pointer',
22 | toleranceElement: '> div',
23 | isTree: true,
24 | startCollapsed: false,
25 | //startCollapsed: $(this).data("start-collapsed"),
26 |
27 | relocate: function(){
28 | //$(this).nestedSortable("disable");
29 | var data = $(this).nestedSortable("serialize");
30 | var url = $(this).data("sortable-url");
31 |
32 | // update on server
33 | $.ajax({
34 | url: url,
35 | type: "post",
36 | data: data
37 | }).always(function(){
38 | //$(this).nestedSortable("enable");
39 |
40 | $(this).find('.item').each(function(index){
41 | if (index % 2){
42 | $(this).removeClass('odd').addClass('even');
43 | }else{
44 | $(this).removeClass('even').addClass('odd');
45 | }
46 | });
47 |
48 | }).done(function(data){
49 |
50 | }).fail(function(jqXHR, textStatus){
51 |
52 | });
53 |
54 |
55 | //$(this).nestedSortable("enable");
56 | }
57 | }); // nested tree
58 | });
59 |
60 |
61 | }); // document.ready
62 |
--------------------------------------------------------------------------------
/lib/assets/stylesheets/sortable_tree.scss:
--------------------------------------------------------------------------------
1 | $cOddRowBackground: #f4f5f5;
2 | $cRowBorder: #e8e8e8;
3 | $cRowSelected: #d9e4ec;
4 | $cRowError: rgb(255,87,87);
5 |
6 | .ui-sortable > *{
7 | cursor: move;
8 | }
9 |
10 |
11 | .disclose{
12 | cursor: pointer;
13 | width: 10px;
14 | display: none;
15 | }
16 |
17 |
18 |
19 | ol.sortable_tree{
20 | margin: 16px 0px 16px 0px;
21 | padding-left: 0px;
22 | }
23 |
24 | .sortable_tree_wrapper {
25 | .left {
26 | float: left !important;
27 | }
28 | .right {
29 | float: right !important;
30 | }
31 |
32 | ol {
33 | list-style-type:none;
34 |
35 | li {
36 | cursor: default !important;
37 |
38 | &.placeholder {
39 | background: lighten($cOddRowBackground, 10%);
40 | border: 1px dashed $cRowSelected;
41 | //.box-sizing(border-box);
42 | -webkit-box-sizing: border-box;
43 | -moz-box-sizing: border-box;
44 | box-sizing: border-box;
45 |
46 | &.cantdoit {
47 | border: 1px dashed $cRowError;
48 | }
49 | }
50 |
51 | .item {
52 | width: 100%;
53 | border-top: 1px solid $cRowBorder;
54 | border-bottom: 1px solid $cRowBorder;
55 |
56 | //.clearfix;
57 | &::after {
58 | content: "";
59 | display: table;
60 | clear: both;
61 | }
62 |
63 | &.even {
64 | background: white;
65 | }
66 |
67 | &.odd {
68 | background: $cOddRowBackground;
69 | }
70 |
71 | &:hover {
72 | background-color: $cRowSelected;
73 | cursor: move;
74 | }
75 |
76 | .cell {
77 | margin: 0;
78 | padding: 10px 12px 8px 12px;
79 | }
80 |
81 | h3.cell {
82 | font-size: 16px;
83 | line-height: 14px;
84 | color: black;
85 | }
86 | }
87 | }
88 |
89 | > li > ol {
90 | margin-left: 30px;
91 | }
92 |
93 | li.mjs-nestedSortable-collapsed > ol {
94 | display: none;
95 | }
96 |
97 | li.mjs-nestedSortable-branch > div > .disclose {
98 | display: block;
99 | float: left;
100 | padding: 10px 5px 8px 5px;
101 | }
102 |
103 | li.mjs-nestedSortable-collapsed > div > .disclose > span:before {
104 | content: '+ ';
105 | }
106 |
107 | li.mjs-nestedSortable-expanded > div > .disclose > span:before {
108 | content: '- ';
109 | }
110 | }
111 | }
112 |
113 |
--------------------------------------------------------------------------------
/lib/sortable_tree_rails.rb:
--------------------------------------------------------------------------------
1 | require "sortable_tree_rails/engine"
2 |
3 | module SortableTreeRails
4 | end
5 |
--------------------------------------------------------------------------------
/lib/sortable_tree_rails/engine.rb:
--------------------------------------------------------------------------------
1 | module SortableTreeRails
2 | class Engine < ::Rails::Engine
3 |
4 |
5 |
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/sortable_tree_rails/version.rb:
--------------------------------------------------------------------------------
1 | module SortableTreeRails
2 | VERSION = "0.0.10"
3 | end
4 |
--------------------------------------------------------------------------------
/lib/tasks/sortable_tree_rails_tasks.rake:
--------------------------------------------------------------------------------
1 | # desc "Explaining what the task does"
2 | # task :sortable_tree_rails do
3 | # # Task goes here
4 | # end
5 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # SortableTreeRails
2 |
3 | GUI for sortable tree to manage data organized in tree with ancestry gem.
4 |
5 | Works with Rails 5, Rails 4.
6 |
7 |
8 | # Overview
9 |
10 | The gem uses:
11 | * [jquery-sortable plugin](https://johnny.github.io/jquery-sortable/) - jQuery Sortable UI
12 | * [nestedSortable](https://github.com/ilikenwf/nestedSortable) - a jQuery plugin for nested lists which extends jQuery Sortable UI
13 |
14 | # Demo
15 | See demo app in the repository (spec/dummy).
16 | Run page with the tree: `http://localhost:3000/categories/manage`.
17 |
18 |
19 | # Usage
20 |
21 |
22 |
23 | ### Gemfile
24 |
25 | ```
26 | gem 'haml-rails'
27 |
28 | gem 'jquery-rails'
29 | gem "jquery-ui-rails"
30 |
31 | gem 'ancestry'
32 | gem 'sortable_tree_rails'
33 | ```
34 |
35 | * the gem uses HAML
36 |
37 |
38 | ### routes
39 |
40 | ```
41 | # config/routes.rb
42 |
43 | resources :categories do
44 | collection do
45 | post :sort
46 | end
47 | end
48 |
49 | ```
50 |
51 | This page (sort_categories_path) will be used by the gem to update data after drag&drop.
52 |
53 | ### model
54 |
55 | It assumes that your model has already fields in DB for ancestry.
56 |
57 | ```
58 | # app/models/category.rb
59 |
60 | class Category < ActiveRecord::Base
61 |
62 | has_ancestry
63 |
64 | # it uses column ancestry_depth
65 | # has_ancestry :cache_depth=>true
66 |
67 | end
68 |
69 | ```
70 |
71 |
72 | ### Javascript
73 |
74 | Include js files in your assets file `application.js`:
75 |
76 | ```
77 | //= require jquery2
78 | //= require jquery-ui
79 |
80 | //=require jquery.mjs.nestedSortable.js
81 | //=require sortable_tree.js
82 |
83 |
84 | ```
85 |
86 | it depends on jquery and jquery-ui which come from gems 'jquery-rails', 'jquery-ui-rails'.
87 | Change `application.js` to include jquery and jquery-ui (jquery-ui/sortable) if you are not using these gems.
88 |
89 |
90 |
91 |
92 | ### CSS
93 |
94 | Add CSS file to your styles.
95 |
96 | for SCSS (app/assets/application.scss):
97 |
98 | ```
99 | ... your css here ..
100 |
101 | @import "sortable_tree";
102 |
103 |
104 | ```
105 |
106 |
107 | ### controller
108 |
109 | ```
110 | class CategoriesController < ApplicationController
111 | include SortableTreeController::Sort
112 | sortable_tree 'Category', {parent_method: 'parent'}
113 |
114 | def manage
115 | # get items to show in tree
116 | @items = Category.all.arrange(:order => :pos)
117 |
118 | end
119 |
120 | end
121 |
122 | ```
123 |
124 | ### view
125 |
126 | ```
127 | # app/views/categories/manage.html.haml
128 |
129 | = render_sortable_tree(@items, {name_method: :name, sort_url: sort_categories_url, max_levels: 5, controls_partial: 'controls'})
130 |
131 | ```
132 |
133 |
134 | ```
135 | # app/views/categories/_controls.html.haml
136 |
137 | = link_to 'Edit', edit_category_url(item)
138 | = link_to 'Delete', category_url(item), :method => :delete, :data => { :confirm => 'Are you sure?' }
139 |
140 | ```
141 |
142 |
143 |
144 | # Customize
145 |
146 | ## Options
147 |
148 | ### Options for controller
149 |
150 | in controller:
151 | ```
152 |
153 | include SortableTreeController::Sort
154 | sortable_tree 'ClassName', {_options_here_}
155 |
156 | ```
157 |
158 |
159 | * ClassName - class name (camel case). For example, 'Category'.
160 | * :sorting_attribute - attribute used for sorting. Set to nil to disable sorting.
161 | * :parent_method - method used to access parent for the item. Set to nil to disable tree (not updating parent).
162 |
163 | Examples.
164 |
165 | * Example. For model with ancestry.
166 | If you use ancestry in model - set :parent_method to 'parent'.
167 |
168 | ```
169 | include SortableTreeController::Sort
170 | sortable_tree 'ClassName', {parent_method: 'parent', sorting_attribute: 'pos'}
171 | ```
172 |
173 | * Example. Do only sorting, without tree.
174 |
175 | ```
176 | include SortableTreeController::Sort
177 | sortable_tree 'ClassName', {sorting_attribute: 'pos', parent_method: nil}
178 | ```
179 |
180 | * Example. No sorting, update only parent.
181 |
182 | ```
183 | include SortableTreeController::Sort
184 | sortable_tree 'ClassName', {sorting_attribute: nil, parent_method: 'parent'}
185 | ```
186 |
187 |
188 |
189 | ## Options for view
190 |
191 | ```
192 | = render_sortable_tree(@items, {__options_here})
193 | ```
194 |
195 | * :name_method - defined which model method (usually, a column name) will be used to show name (default: :name)
196 | * :sort_url - URL used to update data after item is moved to a new position
197 | * :max_levels - max levels to show in tree (default: 5)
198 | * :controls_partial - specify what partial view to use to show control links for each item in a tree. Set to nil to not show controls.
199 |
200 |
201 |
202 | * example.
203 |
204 | ```
205 | = render_sortable_tree(@items, {name_method: :name, sort_url: sort_categories_url, max_levels: 5})
206 | ```
207 |
208 |
209 | * example of partial view with controls.
210 |
211 | * main view:
212 | ```
213 | = render_sortable_tree(@items, {name_method: :name, sort_url: sort_categories_url, max_levels: 5, controls_partial: 'controls'})
214 | ```
215 |
216 | * partial with controls. Use local variable `item` in the partial view.
217 |
218 | `_controls.html.haml`:
219 |
220 | ```
221 | = link_to 'Edit', edit_category_url(item)
222 | = link_to 'Delete', category_url(item), :method => :delete, :data => { :confirm => 'Are you sure?' }
223 |
224 | ```
225 |
226 |
227 | # Customize
228 |
229 | ## customize view
230 |
231 | * edit file 'views/sortable/_sortable.html.haml' to access the whole layout
232 |
233 |
234 |
235 |
236 |
237 | # How it works
238 |
239 | read Wiki
240 |
241 |
242 | # Similar gems
243 |
244 | GUI for sortable tree with awesome_nested_set gem:
245 | * https://github.com/the-teacher/the_sortable_tree
246 | * https://github.com/winescout/the_sortable_tree
247 |
248 |
249 | # Credits
250 | * Some pieces of code was created by inspiration of gem [ActiveAdmin Sortable Tree](https://github.com/maxivak/activeadmin-sortable-tree/)
251 |
252 |
253 |
--------------------------------------------------------------------------------
/sortable_tree_rails.gemspec:
--------------------------------------------------------------------------------
1 | $:.push File.expand_path("../lib", __FILE__)
2 |
3 | # Maintain your gem's version:
4 | require "sortable_tree_rails/version"
5 |
6 | # Describe your gem and declare its dependencies:
7 | Gem::Specification.new do |s|
8 | s.name = "sortable_tree_rails"
9 | s.version = SortableTreeRails::VERSION
10 | s.authors = ["Max Ivak"]
11 | s.email = ["maxivak@gmail.com"]
12 | s.homepage = "https://github.com/maxivak/sortable_tree_rails"
13 | s.summary = "GUI for sortable tree with ancestry gem"
14 | s.description = "GUI for sortable tree to manage data organized in tree with ancestry gem."
15 | s.license = "MIT"
16 |
17 | s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "readme.md"]
18 |
19 | s.add_dependency "rails", ">= 4.2.7"
20 |
21 | s.add_development_dependency "sqlite3"
22 | end
23 |
--------------------------------------------------------------------------------
/spec/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require File.expand_path('../config/application', __FILE__)
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxivak/sortable_tree_rails/949f1c8665bf7c393a191098e9cdf685ecba8a0f/spec/dummy/app/assets/images/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 |
14 | //= require jquery2
15 | //= require jquery-ui
16 |
17 | //=require jquery.mjs.nestedSortable.js
18 | //=require sortable_tree.js
19 |
20 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/stylesheets/application.scss:
--------------------------------------------------------------------------------
1 | @import "sortable_tree";
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | # Prevent CSRF attacks by raising an exception.
3 | # For APIs, you may want to use :null_session instead.
4 | protect_from_forgery with: :exception
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/categories_controller.rb:
--------------------------------------------------------------------------------
1 | class CategoriesController < ApplicationController
2 | protect_from_forgery with: :null_session
3 |
4 | def index
5 |
6 | end
7 |
8 | include SortableTreeController::Sort
9 | sortable_tree 'Category', {parent_method: 'parent', sorting_attribute: 'pos'}
10 | #sortable_tree 'Category', {parent_method: nil, sorting_attribute: nil}
11 |
12 | def manage
13 | # fix ancestry
14 | #Category.build_ancestry_from_parent_ids!
15 |
16 | #
17 | @items = Category.all.arrange(:order => :pos)
18 |
19 | end
20 |
21 |
22 | end
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxivak/sortable_tree_rails/949f1c8665bf7c393a191098e9cdf685ecba8a0f/spec/dummy/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxivak/sortable_tree_rails/949f1c8665bf7c393a191098e9cdf685ecba8a0f/spec/dummy/app/mailers/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxivak/sortable_tree_rails/949f1c8665bf7c393a191098e9cdf685ecba8a0f/spec/dummy/app/models/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/models/category.rb:
--------------------------------------------------------------------------------
1 | class Category < ActiveRecord::Base
2 | #
3 | has_ancestry :cache_depth=>true
4 | # it uses column ancestry_depth
5 |
6 |
7 | end
8 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxivak/sortable_tree_rails/949f1c8665bf7c393a191098e9cdf685ecba8a0f/spec/dummy/app/models/concerns/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/views/categories/_controls.html.haml:
--------------------------------------------------------------------------------
1 | = link_to 'Edit', edit_category_url(item)
2 | = link_to 'Delete', category_url(item), :method => :delete, :data => { :confirm => 'Are you sure?' }
--------------------------------------------------------------------------------
/spec/dummy/app/views/categories/index.html.haml:
--------------------------------------------------------------------------------
1 | %h1 Categories
2 |
3 | %ul
4 | - @items.each do |item|
5 | %li
6 | =item.name
7 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/categories/manage.html.haml:
--------------------------------------------------------------------------------
1 | = render_sortable_tree(@items, {name_method: :name, sort_url: sort_categories_url, max_levels: 5, controls_partial: 'controls'})
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
6 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
7 | <%= csrf_meta_tags %>
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/spec/dummy/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby.exe
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby.exe
2 | APP_PATH = File.expand_path('../../config/application', __FILE__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby.exe
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/spec/dummy/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby.exe
2 | require 'pathname'
3 |
4 | # path to your application root.
5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
6 |
7 | Dir.chdir APP_ROOT do
8 | # This script is a starting point to setup your application.
9 | # Add necessary setup steps to this file:
10 |
11 | puts "== Installing dependencies =="
12 | system "gem install bundler --conservative"
13 | system "bundle check || bundle install"
14 |
15 | # puts "\n== Copying sample files =="
16 | # unless File.exist?("config/database.yml")
17 | # system "cp config/database.yml.sample config/database.yml"
18 | # end
19 |
20 | puts "\n== Preparing database =="
21 | system "bin/rake db:setup"
22 |
23 | puts "\n== Removing old logs and tempfiles =="
24 | system "rm -f log/*"
25 | system "rm -rf tmp/cache"
26 |
27 | puts "\n== Restarting application server =="
28 | system "touch tmp/restart.txt"
29 | end
30 |
--------------------------------------------------------------------------------
/spec/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Rails.application
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | # Pick the frameworks you want:
4 | require "active_record/railtie"
5 | require "action_controller/railtie"
6 | require "action_mailer/railtie"
7 | require "action_view/railtie"
8 | require "sprockets/railtie"
9 | # require "rails/test_unit/railtie"
10 |
11 | Bundler.require(*Rails.groups)
12 | require "sortable_tree_rails"
13 |
14 | module Dummy
15 | class Application < Rails::Application
16 | # Settings in config/environments/* take precedence over those specified here.
17 | # Application configuration should go into files in config/initializers
18 | # -- all .rb files in that directory are automatically loaded.
19 |
20 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
21 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
22 | # config.time_zone = 'Central Time (US & Canada)'
23 |
24 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
25 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
26 | # config.i18n.default_locale = :de
27 |
28 | # Do not swallow errors in after_commit/after_rollback callbacks.
29 | #config.active_record.raise_in_transactional_callbacks = true
30 | end
31 | end
32 |
33 |
--------------------------------------------------------------------------------
/spec/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | # Set up gems listed in the Gemfile.
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
3 |
4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: 5
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
26 |
--------------------------------------------------------------------------------
/spec/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports and disable caching.
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send.
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger.
20 | config.active_support.deprecation = :log
21 |
22 | # Raise an error on page load if there are pending migrations.
23 | config.active_record.migration_error = :page_load
24 |
25 | # Debug mode disables concatenation and preprocessing of assets.
26 | # This option may cause significant delays in view rendering with a large
27 | # number of complex assets.
28 | config.assets.debug = true
29 |
30 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
31 | # yet still be able to expire them through the digest params.
32 | config.assets.digest = true
33 |
34 | # Adds additional error checking when serving assets at runtime.
35 | # Checks for improperly declared sprockets dependencies.
36 | # Raises helpful error messages.
37 | config.assets.raise_runtime_errors = true
38 |
39 | # Raises error for missing translations
40 | # config.action_view.raise_on_missing_translations = true
41 | end
42 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 | # Add `rack-cache` to your Gemfile before enabling this.
19 | # For large-scale production use, consider using a caching reverse proxy like
20 | # NGINX, varnish or squid.
21 | # config.action_dispatch.rack_cache = true
22 |
23 | # Disable serving static files from the `/public` folder by default since
24 | # Apache or NGINX already handles this.
25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
26 |
27 | # Compress JavaScripts and CSS.
28 | config.assets.js_compressor = :uglifier
29 | # config.assets.css_compressor = :sass
30 |
31 | # Do not fallback to assets pipeline if a precompiled asset is missed.
32 | config.assets.compile = false
33 |
34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
35 | # yet still be able to expire them through the digest params.
36 | config.assets.digest = true
37 |
38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
39 |
40 | # Specifies the header that your server uses for sending files.
41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
43 |
44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
45 | # config.force_ssl = true
46 |
47 | # Use the lowest log level to ensure availability of diagnostic information
48 | # when problems arise.
49 | config.log_level = :debug
50 |
51 | # Prepend all log lines with the following tags.
52 | # config.log_tags = [ :subdomain, :uuid ]
53 |
54 | # Use a different logger for distributed setups.
55 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
56 |
57 | # Use a different cache store in production.
58 | # config.cache_store = :mem_cache_store
59 |
60 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
61 | # config.action_controller.asset_host = 'http://assets.example.com'
62 |
63 | # Ignore bad email addresses and do not raise email delivery errors.
64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
65 | # config.action_mailer.raise_delivery_errors = false
66 |
67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
68 | # the I18n.default_locale when a translation cannot be found).
69 | config.i18n.fallbacks = true
70 |
71 | # Send deprecation notices to registered listeners.
72 | config.active_support.deprecation = :notify
73 |
74 | # Use default logging formatter so that PID and timestamp are not suppressed.
75 | config.log_formatter = ::Logger::Formatter.new
76 |
77 | # Do not dump schema after migrations.
78 | config.active_record.dump_schema_after_migration = false
79 | end
80 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure static file server for tests with Cache-Control for performance.
16 | config.serve_static_files = true
17 | config.static_cache_control = 'public, max-age=3600'
18 |
19 | # Show full error reports and disable caching.
20 | config.consider_all_requests_local = true
21 | config.action_controller.perform_caching = false
22 |
23 | # Raise exceptions instead of rendering exception templates.
24 | config.action_dispatch.show_exceptions = false
25 |
26 | # Disable request forgery protection in test environment.
27 | config.action_controller.allow_forgery_protection = false
28 |
29 | # Tell Action Mailer not to deliver emails to the real world.
30 | # The :test delivery method accumulates sent emails in the
31 | # ActionMailer::Base.deliveries array.
32 | config.action_mailer.delivery_method = :test
33 |
34 | # Randomize the order test cases are executed.
35 | config.active_support.test_order = :random
36 |
37 | # Print deprecation notices to the stderr.
38 | config.active_support.deprecation = :stderr
39 |
40 | # Raises error for missing translations
41 | # config.action_view.raise_on_missing_translations = true
42 | end
43 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Add additional assets to the asset load path
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.scss, and all non-JS/CSS in app/assets folder are already added.
11 | # Rails.application.config.assets.precompile += %w( search.js )
12 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.action_dispatch.cookies_serializer = :json
4 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.session_store :cookie_store, key: '_dummy_session'
4 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/spec/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/spec/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | # The priority is based upon order of creation: first created -> highest priority.
3 | # See how all your routes lay out with "rake routes".
4 |
5 | # You can have the root of your site routed with "root"
6 | # root 'welcome#index'
7 |
8 | resources :categories do
9 | collection do
10 | get :manage
11 | post :sort
12 | end
13 |
14 | end
15 |
16 |
17 | # Example of regular route:
18 | # get 'products/:id' => 'catalog#view'
19 |
20 | # Example of named route that can be invoked with purchase_url(id: product.id)
21 | # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
22 |
23 | # Example resource route (maps HTTP verbs to controller actions automatically):
24 | # resources :products
25 |
26 | # Example resource route with options:
27 | # resources :products do
28 | # member do
29 | # get 'short'
30 | # post 'toggle'
31 | # end
32 | #
33 | # collection do
34 | # get 'sold'
35 | # end
36 | # end
37 |
38 | # Example resource route with sub-resources:
39 | # resources :products do
40 | # resources :comments, :sales
41 | # resource :seller
42 | # end
43 |
44 | # Example resource route with more complex sub-resources:
45 | # resources :products do
46 | # resources :comments
47 | # resources :sales do
48 | # get 'recent', on: :collection
49 | # end
50 | # end
51 |
52 | # Example resource route with concerns:
53 | # concern :toggleable do
54 | # post 'toggle'
55 | # end
56 | # resources :posts, concerns: :toggleable
57 | # resources :photos, concerns: :toggleable
58 |
59 | # Example resource route within a namespace:
60 | # namespace :admin do
61 | # # Directs /admin/products/* to Admin::ProductsController
62 | # # (app/controllers/admin/products_controller.rb)
63 | # resources :products
64 | # end
65 | end
66 |
--------------------------------------------------------------------------------
/spec/dummy/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rake secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | development:
14 | secret_key_base: 2a40c0cf27669b3b32e2e18355f927aac5af68be8dcb1f5b9b7b05f9c0c7494067993cf630e495a83cb3dd5b4f45be0d0d8b756da8c6f571f3aab081750409af
15 |
16 | test:
17 | secret_key_base: de88eebcb984b2d91aa7f12462904b7f83d918b8982493351e573444bc6be32ac888ff12fa443d992bd16fc2773482ef9c39ce3611e5e2580ac67e295f2b7173
18 |
19 | # Do not keep production secrets in the repository,
20 | # instead read values from the environment.
21 | production:
22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
23 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20160528172331_create_categories.rb:
--------------------------------------------------------------------------------
1 | class CreateCategories < ActiveRecord::Migration
2 | def change
3 | create_table :categories do |t|
4 | t.string :name
5 | t.text :description
6 |
7 | t.integer :pos
8 | t.integer :parent_id
9 |
10 | t.timestamps null: false
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20160528172947_add_ancestry_to_categories.rb:
--------------------------------------------------------------------------------
1 | class AddAncestryToCategories < ActiveRecord::Migration
2 | def change
3 | add_column :categories, :ancestry, :string
4 | add_column :categories, :ancestry_depth, :integer
5 |
6 | add_index :categories, :ancestry
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/spec/dummy/db/schema.rb:
--------------------------------------------------------------------------------
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 that you check this file into your version control system.
13 |
14 | ActiveRecord::Schema.define(version: 20160528172947) do
15 |
16 | create_table "categories", force: :cascade do |t|
17 | t.string "name"
18 | t.text "description"
19 | t.integer "pos"
20 | t.datetime "created_at", null: false
21 | t.datetime "updated_at", null: false
22 | t.string "ancestry"
23 | t.integer "ancestry_depth"
24 | end
25 |
26 | add_index "categories", ["ancestry"], name: "index_categories_on_ancestry"
27 |
28 | end
29 |
--------------------------------------------------------------------------------
/spec/dummy/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxivak/sortable_tree_rails/949f1c8665bf7c393a191098e9cdf685ecba8a0f/spec/dummy/lib/assets/.keep
--------------------------------------------------------------------------------
/spec/dummy/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxivak/sortable_tree_rails/949f1c8665bf7c393a191098e9cdf685ecba8a0f/spec/dummy/log/.keep
--------------------------------------------------------------------------------
/spec/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/spec/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/spec/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/spec/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxivak/sortable_tree_rails/949f1c8665bf7c393a191098e9cdf685ecba8a0f/spec/dummy/public/favicon.ico
--------------------------------------------------------------------------------