activerecord-sortable 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.rdoc +3 -0
- data/Rakefile +33 -0
- data/lib/activerecord/sortable.rb +8 -0
- data/lib/activerecord/sortable/acts_as_sortable.rb +40 -0
- data/lib/activerecord/sortable/acts_as_sortable/instance_methods.rb +66 -0
- data/lib/activerecord/sortable/rails.rb +8 -0
- data/lib/activerecord/sortable/version.rb +5 -0
- data/lib/assets/javascripts/sortable.js +99 -0
- data/lib/tasks/sortable_tasks.rake +4 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +9 -0
- data/spec/dummy/app/assets/javascripts/jquery.simulate.js +312 -0
- data/spec/dummy/app/assets/stylesheets/application.css +14 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/things_controller.rb +10 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/item.rb +3 -0
- data/spec/dummy/app/models/other_thing.rb +5 -0
- data/spec/dummy/app/models/thing.rb +3 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/things/_thing.html.erb +3 -0
- data/spec/dummy/app/views/things/index.html.erb +7 -0
- data/spec/dummy/app/views/things/move.js.erb +4 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +24 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +29 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +9 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20140512100816_create_things.rb +10 -0
- data/spec/dummy/db/migrate/20140525104834_create_other_things.rb +10 -0
- data/spec/dummy/db/migrate/20140525111906_create_items.rb +7 -0
- data/spec/dummy/db/migrate/20140525112123_add_position_to_items.rb +13 -0
- data/spec/dummy/db/schema.rb +40 -0
- data/spec/dummy/db/seeds.rb +3 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +5000 -0
- data/spec/dummy/log/test.log +125455 -0
- data/spec/dummy/public/404.html +58 -0
- data/spec/dummy/public/422.html +58 -0
- data/spec/dummy/public/500.html +57 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/02e6da2de0eeda095c3ab81e1033fc1d +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/04cf541b9d1ee4cad14421becb051689 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/06a48cadb2e97ab6c9c35285891cd326 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/0bb2483991c2c5f7f83fde21edafb6a9 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/0d3c3b294494fb664545d3c26ee18f14 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/0de48e81714433e3a37eac8d8be3d97e +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/10ae91021bb1bbd0930672c779b3b91a +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/11820a6ecc13dd7f7094529f02abab32 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/11854112d39135b018adc8440defeda0 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/1510c8e329d6e9494521d212f6436149 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/1f6d40a549a87bc8b9dc59afe0b41143 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/30e32f924769f4b2ef4ec2ed78d62f3d +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/3102e67dc53e4d0f35c4581e16261889 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/327b885c1a9d58094da4fda60431a466 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/327f7f624a6fc56d3d434e46aceb3299 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/3754f0eb2c14b3faa11aec78b2dcee56 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/3a8816f3d1d27361f67808fed1cfaf48 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/3c6d7669755b971545e296fc93a9595b +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/3e904cfb843c2a95c02c07b4fbc96e6e +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/402789e80a4ebf130058eeae9bacd1e3 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/405ef6e6ee991e0b2d850f3443245dc8 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/4249e053e25b3a5632832c437bfd0e4d +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/42b8d23a55d28e131ae662ed762cbf08 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/4646b96b524c218d2b80992e633080ff +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/47e71eb31da5be13ced7c7a889342d58 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/485c8ac4edde07df3f326e4f4b9f9106 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/49bf73b3490c4a8e62534bd0d5be81c3 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/4c1e2286914bc9b9ad1073e0aaa47a40 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/4c803caf54aa7538b898209acc1b1070 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/562dbca143b36a28d7fc6820500de6cc +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/57c480a56fb25b20eb672e4027117550 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/58b84f82ca3fe739a2c21accf3b43e08 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/5b848793a8918c09f06e30db36aeb816 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/5dc29296d9d4d74645d70a5225f6027f +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/61832b49e09217de656ff5bd787626c9 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/6386af6a5c718c1ddd97f609e0f7e9bd +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/63c434ac472c04632133d9bc2f8ebe44 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/64ef40135516dbbf1737a2d598b22cfa +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/67ad3a3d9a5fa9dd07456e131ce9c2a8 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/6c2e45e1a31edecd0e3584aa2d836c58 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/743ab64e3ac90ba4734a5a1af916fd93 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/77088c2f43bec60ff0e030676e96f686 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/777746e36b5f546628033e633d960005 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/7ed5d11be8a68c295f8833e4961891ff +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/80d43205b7942ece8d8b09f859813908 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/867c6ad0b26a9665bc431c93887cea11 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/87f7d23ea55311ee3affe8e6ad8f1b57 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/8a23854402292f417575b8ecaa5ced06 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/8b39a75a80c1fb3a07395e674644e2cc +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/8fd0bc56f8581ee9a4376dced1efc158 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/a12ecc7f4b8b7b1315023a6f62aa4ab5 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/a2038db6aaa5289fba7fde9aea561a08 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/ad0693eaa72c0eee52a255fb9d030503 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/b0a786e48e0e908ab34cfbb918853146 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/b84a4d17f27ef190e1eed6129eaac20b +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/b9296322253567156d260263305d22d2 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/b970eb3cff83ab8da0076e1472860d75 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/bbceab6aa0252d0552d3a37d2d11e98d +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/bfca8c57a5fdc4f3f2e41cea232ed987 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/bfce4ec4b73dd7cbfa1a9dcc601c5050 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/bfd7da030142aceda83eb82f60b33487 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/c7e351cd01a8592503c63b37d7d69e4f +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/caf21088467d229770a205c793606696 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/d1256d5e44bddbb67bfb24dbab1c3795 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/d2c66e0a8aa45b5f5b17c98c3d39bf91 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/d3fe7bc39b02f61c0f994820aec91f78 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/d4b2ad85aa1a4f1ea28a39b5518f992b +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/d5449ece5f819190ad6dd88c1caccffc +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/d8b62b055a9e2933d39777275cd166f0 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/dbbb41e059e78bdfbb7ec9285ce21b97 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/e0f01bdea42cc99228735a8a21d95ef2 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/ebd00f71b7dcba8d80316e0b8d5e76e3 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/ebe17685dedaf8f574dde1463bdea118 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/f188fdc5d08872237a3dd0231bbfe4e1 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/f4cbaca85a4be542050fd39cbf5eb6c3 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/fba7f1b57d7cc3253cb40677cbbd8f54 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/fd13af7854ecfb6bedf12ce71539b636 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/02e6da2de0eeda095c3ab81e1033fc1d +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/0bb2483991c2c5f7f83fde21edafb6a9 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/11820a6ecc13dd7f7094529f02abab32 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/405ef6e6ee991e0b2d850f3443245dc8 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/485c8ac4edde07df3f326e4f4b9f9106 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/63c434ac472c04632133d9bc2f8ebe44 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/67ad3a3d9a5fa9dd07456e131ce9c2a8 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/77088c2f43bec60ff0e030676e96f686 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/80d43205b7942ece8d8b09f859813908 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/a2038db6aaa5289fba7fde9aea561a08 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/bbceab6aa0252d0552d3a37d2d11e98d +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/c7e351cd01a8592503c63b37d7d69e4f +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/caf21088467d229770a205c793606696 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/d8b62b055a9e2933d39777275cd166f0 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/ebe17685dedaf8f574dde1463bdea118 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/fba7f1b57d7cc3253cb40677cbbd8f54 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/fd13af7854ecfb6bedf12ce71539b636 +0 -0
- data/spec/features/drag_and_drop_spec.rb +65 -0
- data/spec/other_thing_spec.rb +5 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/activerecord_sortable.rb +181 -0
- data/spec/thing_spec.rb +5 -0
- metadata +458 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d99827ae2d5921a9e848eac321ae02850a4f2a6f
|
4
|
+
data.tar.gz: b838570af229fa92372b6459666f4469f67e70ac
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0ac4f1819d8e111a271364995d75cd43d7fe2315cfc89140efd16fda08313ddb0f3a09e31741da779a81830c377c4c342e40422fafdb22ef54f4ce2891ce689d
|
7
|
+
data.tar.gz: 03d4aa1bd6843d6d2a9e2fadddefd570d3d74950072a56d8f041c872274eff3cf5c3acdd713298a7fbd340a69bccd7c35c5fcde08519cf8b61c5805b4cddda5d
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
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
|
+
|
8
|
+
require 'rdoc/task'
|
9
|
+
|
10
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
11
|
+
rdoc.rdoc_dir = 'rdoc'
|
12
|
+
rdoc.title = 'Sortable'
|
13
|
+
rdoc.options << '--line-numbers'
|
14
|
+
rdoc.rdoc_files.include('README.rdoc')
|
15
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
20
|
+
|
21
|
+
load 'rails/tasks/engine.rake'
|
22
|
+
|
23
|
+
Bundler::GemHelper.install_tasks
|
24
|
+
|
25
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
|
26
|
+
|
27
|
+
require 'rspec/core'
|
28
|
+
require 'rspec/core/rake_task'
|
29
|
+
|
30
|
+
desc "Run all specs in spec directory (excluding plugin specs)"
|
31
|
+
RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
|
32
|
+
|
33
|
+
task :default => :spec
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'activerecord/sortable/acts_as_sortable/instance_methods'
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Sortable
|
7
|
+
module ActsAsSortable
|
8
|
+
extend ::ActiveSupport::Concern
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def acts_as_sortable(&block)
|
12
|
+
options = {
|
13
|
+
:relation => ->(instance) {instance.class},
|
14
|
+
:append => false,
|
15
|
+
:position_column => :position
|
16
|
+
}
|
17
|
+
block.call(options) if block_given?
|
18
|
+
|
19
|
+
cattr_accessor :sortable_relation, :instance_reader => false, :instance_writer => false
|
20
|
+
cattr_accessor :sortable_append, :instance_reader => true, :instance_writer => false
|
21
|
+
cattr_accessor :sortable_position_column, :instance_reader => true, :instance_writer => false
|
22
|
+
|
23
|
+
self.sortable_relation = options[:relation]
|
24
|
+
self.sortable_append = options[:append]
|
25
|
+
self.sortable_position_column = options[:position_column]
|
26
|
+
|
27
|
+
scope "ordered_by_#{self.sortable_position_column}_asc".to_sym, -> { order(self.sortable_position_column => :asc) }
|
28
|
+
|
29
|
+
before_create :sortable_set_default_position
|
30
|
+
after_destroy :sortable_shift_on_destroy
|
31
|
+
|
32
|
+
include ActiveRecord::Sortable::ActsAsSortable::InstanceMethods
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
require 'active_record'
|
40
|
+
ActiveRecord::Base.send :include, ActiveRecord::Sortable::ActsAsSortable
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Sortable
|
3
|
+
module ActsAsSortable
|
4
|
+
module InstanceMethods
|
5
|
+
|
6
|
+
def move_to!(new_position)
|
7
|
+
new_position = Integer(new_position)
|
8
|
+
|
9
|
+
transaction do
|
10
|
+
if new_position > self.send(sortable_position_column)
|
11
|
+
sortable_relation
|
12
|
+
.where(["#{sortable_position_column} > ? AND #{sortable_position_column} <= ?", self.send(sortable_position_column), new_position])
|
13
|
+
.update_all sortable_updates_with_timestamps("#{sortable_position_column} = #{sortable_position_column} - 1")
|
14
|
+
else
|
15
|
+
sortable_relation
|
16
|
+
.where(["#{sortable_position_column} >= ? AND #{sortable_position_column} < ?", new_position, self.send(sortable_position_column)])
|
17
|
+
.update_all sortable_updates_with_timestamps("#{sortable_position_column} = #{sortable_position_column} + 1")
|
18
|
+
end
|
19
|
+
|
20
|
+
self.update_attribute(sortable_position_column, new_position)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def sortable_relation
|
28
|
+
self.class.sortable_relation.call(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def sortable_set_default_position
|
32
|
+
sortable_assign_default_position unless self.send(sortable_position_column).present?
|
33
|
+
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
def sortable_assign_default_position
|
38
|
+
if sortable_append
|
39
|
+
next_position = (max_position = sortable_relation.pluck(self.sortable_position_column).max).present? ? max_position + 1 : 0
|
40
|
+
self.send("#{sortable_position_column}=".to_sym, next_position)
|
41
|
+
else
|
42
|
+
sortable_relation.update_all sortable_updates_with_timestamps("#{sortable_position_column} = #{sortable_position_column} + 1")
|
43
|
+
self.send("#{sortable_position_column}=".to_sym, 0)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def sortable_updates_with_timestamps(base_query)
|
48
|
+
current_time = current_time_from_proper_timezone
|
49
|
+
|
50
|
+
query_items = [base_query]
|
51
|
+
|
52
|
+
update_values = timestamp_attributes_for_update_in_model.map do |attr|
|
53
|
+
query_items << "#{attr} = ?"
|
54
|
+
current_time
|
55
|
+
end
|
56
|
+
updates = [query_items.join(', ')] + update_values
|
57
|
+
end
|
58
|
+
|
59
|
+
def sortable_shift_on_destroy
|
60
|
+
sortable_relation.where(["#{sortable_position_column} > ?", self.send(sortable_position_column)]).update_all sortable_updates_with_timestamps("#{sortable_position_column} = #{sortable_position_column} - 1")
|
61
|
+
true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
(function(window, $){
|
2
|
+
if(!$){throw new Error('jQuery required by Sortable')};
|
3
|
+
|
4
|
+
|
5
|
+
function PositionsUpdater(node, current_node, new_position, old_position) {
|
6
|
+
this.node = node;
|
7
|
+
this.current_node = current_node;
|
8
|
+
this.new_position = new_position;
|
9
|
+
this.old_position = old_position;
|
10
|
+
};
|
11
|
+
|
12
|
+
PositionsUpdater.prototype.update = function() {
|
13
|
+
var step = this.new_position > this.old_position ? -1 : 1;
|
14
|
+
var start_position = step === -1 ? this.old_position + 1 : this.new_position;
|
15
|
+
var stop_position = step === -1 ? this.new_position : this.old_position - 1;
|
16
|
+
var target, data_position;
|
17
|
+
var current_node_role = this.current_node.data('role');
|
18
|
+
|
19
|
+
$('*[data-position]', this.node)
|
20
|
+
.filter(function(){
|
21
|
+
var position = $(this).data('position');
|
22
|
+
var role = $(this).data('role');
|
23
|
+
|
24
|
+
return (current_node_role !== role) && position >= start_position && position <= stop_position;
|
25
|
+
})
|
26
|
+
.each(function(){
|
27
|
+
var target = $(this);
|
28
|
+
|
29
|
+
var data_position = target.data('position');
|
30
|
+
var new_position = data_position + step;
|
31
|
+
|
32
|
+
target.attr('data-position', new_position).data('position', new_position);
|
33
|
+
});
|
34
|
+
};
|
35
|
+
|
36
|
+
|
37
|
+
function ServerSorter(node, current_node, new_position, positions_updater) {
|
38
|
+
this.node = node;
|
39
|
+
this.current_node = current_node;
|
40
|
+
this.new_position = new_position;
|
41
|
+
this.positions_updater = positions_updater;
|
42
|
+
};
|
43
|
+
|
44
|
+
ServerSorter.prototype.sort = function() {
|
45
|
+
$.ajax({
|
46
|
+
type: 'POST',
|
47
|
+
dataType: 'script',
|
48
|
+
url: this.current_node.data('move-url'),
|
49
|
+
data: {
|
50
|
+
position: this.new_position
|
51
|
+
},
|
52
|
+
success: $.proxy(this.sort_by_server_success, this),
|
53
|
+
error: $.proxy(this.sort_by_server_error, this)
|
54
|
+
});
|
55
|
+
};
|
56
|
+
|
57
|
+
ServerSorter.prototype.sort_by_server_success = function() {
|
58
|
+
this.positions_updater.update();
|
59
|
+
this.node.trigger('sortable:sort_success').trigger('sortable:stop').sortable('enable');
|
60
|
+
};
|
61
|
+
|
62
|
+
ServerSorter.prototype.sort_by_server_error = function() {
|
63
|
+
this.node.sortable('cancel').trigger('sortable:sort_error').trigger('sortable:stop').sortable('enable');
|
64
|
+
};
|
65
|
+
|
66
|
+
|
67
|
+
function Sortable(node) {
|
68
|
+
this.node = node;
|
69
|
+
|
70
|
+
this.node.sortable({
|
71
|
+
axis: 'y',
|
72
|
+
update: $.proxy(this.sortable_stop_handler, this)
|
73
|
+
});
|
74
|
+
};
|
75
|
+
|
76
|
+
Sortable.prototype.sortable_stop_handler = function(e, ui) {
|
77
|
+
this.node.sortable('disable').trigger('sortable:start');
|
78
|
+
|
79
|
+
var node = ui.item;
|
80
|
+
var new_position = node.index();
|
81
|
+
var old_position = parseInt(node.data('position'));
|
82
|
+
|
83
|
+
var positions_updater = new PositionsUpdater(this.node, node, new_position, old_position);
|
84
|
+
|
85
|
+
server_sorter = new ServerSorter(this.node, node, new_position, positions_updater);
|
86
|
+
server_sorter.sort();
|
87
|
+
};
|
88
|
+
|
89
|
+
|
90
|
+
$.fn.activerecord_sortable = function() {
|
91
|
+
var target = $(this);
|
92
|
+
target.each(function(){
|
93
|
+
var self = $(this);
|
94
|
+
var sortable_instance = new Sortable(self);
|
95
|
+
|
96
|
+
self.data('sortable-instance', sortable_instance);
|
97
|
+
});
|
98
|
+
}
|
99
|
+
})(window, window.jQuery);
|
@@ -0,0 +1,28 @@
|
|
1
|
+
== README
|
2
|
+
|
3
|
+
This README would normally document whatever steps are necessary to get the
|
4
|
+
application up and running.
|
5
|
+
|
6
|
+
Things you may want to cover:
|
7
|
+
|
8
|
+
* Ruby version
|
9
|
+
|
10
|
+
* System dependencies
|
11
|
+
|
12
|
+
* Configuration
|
13
|
+
|
14
|
+
* Database creation
|
15
|
+
|
16
|
+
* Database initialization
|
17
|
+
|
18
|
+
* How to run the test suite
|
19
|
+
|
20
|
+
* Services (job queues, cache servers, search engines, etc.)
|
21
|
+
|
22
|
+
* Deployment instructions
|
23
|
+
|
24
|
+
* ...
|
25
|
+
|
26
|
+
|
27
|
+
Please feel free to use a different markup language if you do not plan to run
|
28
|
+
<tt>rake doc:app</tt>.
|
data/spec/dummy/Rakefile
ADDED
@@ -0,0 +1,312 @@
|
|
1
|
+
/*!
|
2
|
+
* jQuery Simulate v@VERSION - simulate browser mouse and keyboard events
|
3
|
+
* https://github.com/jquery/jquery-simulate
|
4
|
+
*
|
5
|
+
* Copyright 2012 jQuery Foundation and other contributors
|
6
|
+
* Released under the MIT license.
|
7
|
+
* http://jquery.org/license
|
8
|
+
*
|
9
|
+
* Date: @DATE
|
10
|
+
*/
|
11
|
+
|
12
|
+
;(function( $, undefined ) {
|
13
|
+
|
14
|
+
var rkeyEvent = /^key/,
|
15
|
+
rmouseEvent = /^(?:mouse|contextmenu)|click/;
|
16
|
+
|
17
|
+
$.fn.simulate = function( type, options ) {
|
18
|
+
return this.each(function() {
|
19
|
+
new $.simulate( this, type, options );
|
20
|
+
});
|
21
|
+
};
|
22
|
+
|
23
|
+
$.simulate = function( elem, type, options ) {
|
24
|
+
var method = $.camelCase( "simulate-" + type );
|
25
|
+
|
26
|
+
this.target = elem;
|
27
|
+
this.options = options;
|
28
|
+
|
29
|
+
if ( this[ method ] ) {
|
30
|
+
this[ method ]();
|
31
|
+
} else {
|
32
|
+
this.simulateEvent( elem, type, options );
|
33
|
+
}
|
34
|
+
};
|
35
|
+
|
36
|
+
$.extend( $.simulate, {
|
37
|
+
|
38
|
+
keyCode: {
|
39
|
+
BACKSPACE: 8,
|
40
|
+
COMMA: 188,
|
41
|
+
DELETE: 46,
|
42
|
+
DOWN: 40,
|
43
|
+
END: 35,
|
44
|
+
ENTER: 13,
|
45
|
+
ESCAPE: 27,
|
46
|
+
HOME: 36,
|
47
|
+
LEFT: 37,
|
48
|
+
NUMPAD_ADD: 107,
|
49
|
+
NUMPAD_DECIMAL: 110,
|
50
|
+
NUMPAD_DIVIDE: 111,
|
51
|
+
NUMPAD_ENTER: 108,
|
52
|
+
NUMPAD_MULTIPLY: 106,
|
53
|
+
NUMPAD_SUBTRACT: 109,
|
54
|
+
PAGE_DOWN: 34,
|
55
|
+
PAGE_UP: 33,
|
56
|
+
PERIOD: 190,
|
57
|
+
RIGHT: 39,
|
58
|
+
SPACE: 32,
|
59
|
+
TAB: 9,
|
60
|
+
UP: 38
|
61
|
+
},
|
62
|
+
|
63
|
+
buttonCode: {
|
64
|
+
LEFT: 0,
|
65
|
+
MIDDLE: 1,
|
66
|
+
RIGHT: 2
|
67
|
+
}
|
68
|
+
});
|
69
|
+
|
70
|
+
$.extend( $.simulate.prototype, {
|
71
|
+
|
72
|
+
simulateEvent: function( elem, type, options ) {
|
73
|
+
var event = this.createEvent( type, options );
|
74
|
+
this.dispatchEvent( elem, type, event, options );
|
75
|
+
},
|
76
|
+
|
77
|
+
createEvent: function( type, options ) {
|
78
|
+
if ( rkeyEvent.test( type ) ) {
|
79
|
+
return this.keyEvent( type, options );
|
80
|
+
}
|
81
|
+
|
82
|
+
if ( rmouseEvent.test( type ) ) {
|
83
|
+
return this.mouseEvent( type, options );
|
84
|
+
}
|
85
|
+
},
|
86
|
+
|
87
|
+
mouseEvent: function( type, options ) {
|
88
|
+
var event, eventDoc, doc, body;
|
89
|
+
options = $.extend({
|
90
|
+
bubbles: true,
|
91
|
+
cancelable: (type !== "mousemove"),
|
92
|
+
view: window,
|
93
|
+
detail: 0,
|
94
|
+
screenX: 0,
|
95
|
+
screenY: 0,
|
96
|
+
clientX: 1,
|
97
|
+
clientY: 1,
|
98
|
+
ctrlKey: false,
|
99
|
+
altKey: false,
|
100
|
+
shiftKey: false,
|
101
|
+
metaKey: false,
|
102
|
+
button: 0,
|
103
|
+
relatedTarget: undefined
|
104
|
+
}, options );
|
105
|
+
|
106
|
+
if ( document.createEvent ) {
|
107
|
+
event = document.createEvent( "MouseEvents" );
|
108
|
+
event.initMouseEvent( type, options.bubbles, options.cancelable,
|
109
|
+
options.view, options.detail,
|
110
|
+
options.screenX, options.screenY, options.clientX, options.clientY,
|
111
|
+
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
|
112
|
+
options.button, options.relatedTarget || document.body.parentNode );
|
113
|
+
|
114
|
+
// IE 9+ creates events with pageX and pageY set to 0.
|
115
|
+
// Trying to modify the properties throws an error,
|
116
|
+
// so we define getters to return the correct values.
|
117
|
+
if ( event.pageX === 0 && event.pageY === 0 && Object.defineProperty ) {
|
118
|
+
eventDoc = event.relatedTarget.ownerDocument || document;
|
119
|
+
doc = eventDoc.documentElement;
|
120
|
+
body = eventDoc.body;
|
121
|
+
|
122
|
+
Object.defineProperty( event, "pageX", {
|
123
|
+
get: function() {
|
124
|
+
return options.clientX +
|
125
|
+
( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
|
126
|
+
( doc && doc.clientLeft || body && body.clientLeft || 0 );
|
127
|
+
}
|
128
|
+
});
|
129
|
+
Object.defineProperty( event, "pageY", {
|
130
|
+
get: function() {
|
131
|
+
return options.clientY +
|
132
|
+
( doc && doc.scrollTop || body && body.scrollTop || 0 ) -
|
133
|
+
( doc && doc.clientTop || body && body.clientTop || 0 );
|
134
|
+
}
|
135
|
+
});
|
136
|
+
}
|
137
|
+
} else if ( document.createEventObject ) {
|
138
|
+
event = document.createEventObject();
|
139
|
+
$.extend( event, options );
|
140
|
+
// standards event.button uses constants defined here: http://msdn.microsoft.com/en-us/library/ie/ff974877(v=vs.85).aspx
|
141
|
+
// old IE event.button uses constants defined here: http://msdn.microsoft.com/en-us/library/ie/ms533544(v=vs.85).aspx
|
142
|
+
// so we actually need to map the standard back to oldIE
|
143
|
+
event.button = {
|
144
|
+
0: 1,
|
145
|
+
1: 4,
|
146
|
+
2: 2
|
147
|
+
}[ event.button ] || event.button;
|
148
|
+
}
|
149
|
+
|
150
|
+
return event;
|
151
|
+
},
|
152
|
+
|
153
|
+
keyEvent: function( type, options ) {
|
154
|
+
var event;
|
155
|
+
options = $.extend({
|
156
|
+
bubbles: true,
|
157
|
+
cancelable: true,
|
158
|
+
view: window,
|
159
|
+
ctrlKey: false,
|
160
|
+
altKey: false,
|
161
|
+
shiftKey: false,
|
162
|
+
metaKey: false,
|
163
|
+
keyCode: 0,
|
164
|
+
charCode: undefined
|
165
|
+
}, options );
|
166
|
+
|
167
|
+
if ( document.createEvent ) {
|
168
|
+
try {
|
169
|
+
event = document.createEvent( "KeyEvents" );
|
170
|
+
event.initKeyEvent( type, options.bubbles, options.cancelable, options.view,
|
171
|
+
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
|
172
|
+
options.keyCode, options.charCode );
|
173
|
+
// initKeyEvent throws an exception in WebKit
|
174
|
+
// see: http://stackoverflow.com/questions/6406784/initkeyevent-keypress-only-works-in-firefox-need-a-cross-browser-solution
|
175
|
+
// and also https://bugs.webkit.org/show_bug.cgi?id=13368
|
176
|
+
// fall back to a generic event until we decide to implement initKeyboardEvent
|
177
|
+
} catch( err ) {
|
178
|
+
event = document.createEvent( "Events" );
|
179
|
+
event.initEvent( type, options.bubbles, options.cancelable );
|
180
|
+
$.extend( event, {
|
181
|
+
view: options.view,
|
182
|
+
ctrlKey: options.ctrlKey,
|
183
|
+
altKey: options.altKey,
|
184
|
+
shiftKey: options.shiftKey,
|
185
|
+
metaKey: options.metaKey,
|
186
|
+
keyCode: options.keyCode,
|
187
|
+
charCode: options.charCode
|
188
|
+
});
|
189
|
+
}
|
190
|
+
} else if ( document.createEventObject ) {
|
191
|
+
event = document.createEventObject();
|
192
|
+
$.extend( event, options );
|
193
|
+
}
|
194
|
+
|
195
|
+
if ( !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ) || (({}).toString.call( window.opera ) === "[object Opera]") ) {
|
196
|
+
event.keyCode = (options.charCode > 0) ? options.charCode : options.keyCode;
|
197
|
+
event.charCode = undefined;
|
198
|
+
}
|
199
|
+
|
200
|
+
return event;
|
201
|
+
},
|
202
|
+
|
203
|
+
dispatchEvent: function( elem, type, event ) {
|
204
|
+
if ( elem.dispatchEvent ) {
|
205
|
+
elem.dispatchEvent( event );
|
206
|
+
} else if ( elem.fireEvent ) {
|
207
|
+
elem.fireEvent( "on" + type, event );
|
208
|
+
}
|
209
|
+
},
|
210
|
+
|
211
|
+
simulateFocus: function() {
|
212
|
+
var focusinEvent,
|
213
|
+
triggered = false,
|
214
|
+
element = $( this.target );
|
215
|
+
|
216
|
+
function trigger() {
|
217
|
+
triggered = true;
|
218
|
+
}
|
219
|
+
|
220
|
+
element.bind( "focus", trigger );
|
221
|
+
element[ 0 ].focus();
|
222
|
+
|
223
|
+
if ( !triggered ) {
|
224
|
+
focusinEvent = $.Event( "focusin" );
|
225
|
+
focusinEvent.preventDefault();
|
226
|
+
element.trigger( focusinEvent );
|
227
|
+
element.triggerHandler( "focus" );
|
228
|
+
}
|
229
|
+
element.unbind( "focus", trigger );
|
230
|
+
},
|
231
|
+
|
232
|
+
simulateBlur: function() {
|
233
|
+
var focusoutEvent,
|
234
|
+
triggered = false,
|
235
|
+
element = $( this.target );
|
236
|
+
|
237
|
+
function trigger() {
|
238
|
+
triggered = true;
|
239
|
+
}
|
240
|
+
|
241
|
+
element.bind( "blur", trigger );
|
242
|
+
element[ 0 ].blur();
|
243
|
+
|
244
|
+
// blur events are async in IE
|
245
|
+
setTimeout(function() {
|
246
|
+
// IE won't let the blur occur if the window is inactive
|
247
|
+
if ( element[ 0 ].ownerDocument.activeElement === element[ 0 ] ) {
|
248
|
+
element[ 0 ].ownerDocument.body.focus();
|
249
|
+
}
|
250
|
+
|
251
|
+
// Firefox won't trigger events if the window is inactive
|
252
|
+
// IE doesn't trigger events if we had to manually focus the body
|
253
|
+
if ( !triggered ) {
|
254
|
+
focusoutEvent = $.Event( "focusout" );
|
255
|
+
focusoutEvent.preventDefault();
|
256
|
+
element.trigger( focusoutEvent );
|
257
|
+
element.triggerHandler( "blur" );
|
258
|
+
}
|
259
|
+
element.unbind( "blur", trigger );
|
260
|
+
}, 1 );
|
261
|
+
}
|
262
|
+
});
|
263
|
+
|
264
|
+
|
265
|
+
|
266
|
+
/** complex events **/
|
267
|
+
|
268
|
+
function findCenter( elem ) {
|
269
|
+
var offset,
|
270
|
+
document = $( elem.ownerDocument );
|
271
|
+
elem = $( elem );
|
272
|
+
offset = elem.offset();
|
273
|
+
|
274
|
+
return {
|
275
|
+
x: offset.left + elem.outerWidth() / 2 - document.scrollLeft(),
|
276
|
+
y: offset.top + elem.outerHeight() / 2 - document.scrollTop()
|
277
|
+
};
|
278
|
+
}
|
279
|
+
|
280
|
+
$.extend( $.simulate.prototype, {
|
281
|
+
simulateDrag: function() {
|
282
|
+
var i = 0,
|
283
|
+
target = this.target,
|
284
|
+
options = this.options,
|
285
|
+
center = findCenter( target ),
|
286
|
+
x = Math.floor( center.x ),
|
287
|
+
y = Math.floor( center.y ),
|
288
|
+
dx = options.dx || 0,
|
289
|
+
dy = options.dy || 0,
|
290
|
+
moves = options.moves || 3,
|
291
|
+
coord = { clientX: x, clientY: y };
|
292
|
+
|
293
|
+
this.simulateEvent( target, "mousedown", coord );
|
294
|
+
|
295
|
+
for ( ; i < moves ; i++ ) {
|
296
|
+
x += dx / moves;
|
297
|
+
y += dy / moves;
|
298
|
+
|
299
|
+
coord = {
|
300
|
+
clientX: Math.round( x ),
|
301
|
+
clientY: Math.round( y )
|
302
|
+
};
|
303
|
+
|
304
|
+
this.simulateEvent( document, "mousemove", coord );
|
305
|
+
}
|
306
|
+
|
307
|
+
this.simulateEvent( target, "mouseup", coord );
|
308
|
+
this.simulateEvent( target, "click", coord );
|
309
|
+
}
|
310
|
+
});
|
311
|
+
|
312
|
+
})( jQuery );
|