diff --git a/Gemfile b/Gemfile index 20c5d36..886a406 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,10 @@ end gem 'jquery-rails' gem 'jquery-ui-rails' +if ENV['RAILS_VERSION'] == '~> 3.2.0' && (RUBY_VERSION == '1.9.3' || defined?(JRUBY_VERSION)) + gem 'mime-types-data', '3.2015.1120' +end + if defined?(JRUBY_VERSION) gem 'activerecord-jdbcsqlite3-adapter' else diff --git a/README.md b/README.md index a321f4d..d90475a 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,9 @@ $(document).ready(function(){ Before send ajax request to server jQuery UI Sortable disabled, after receive response enable. -`activerecord_sortable()` accetps [JQuery UI Sortable](http://api.jqueryui.com/sortable/)-style options. By default `axis` uses `y` value (to prevent horizontal dragging), and `update` overwrites by internal handler and is unavailabe to set. +`activerecord_sortable()` accepts [JQuery UI Sortable](http://api.jqueryui.com/sortable/)-style options. By default `axis` uses `y` value (to prevent horizontal dragging), and `update` overwrites by internal handler and is unavailabe to set. + +`$('*[data-role=activerecord_sortable]')` node may have `start-position` data-attribute (`
...
`) in this case first element position will be equal to data-attribute value. If `start-position` not set, then first child node data-attribute `position` will be taken, `0` otherwise. ## How to add activerecord-sortable into existing model diff --git a/README.ru.md b/README.ru.md index 5f2eb64..65320e2 100644 --- a/README.ru.md +++ b/README.ru.md @@ -184,6 +184,8 @@ $(document).ready(function(){ `activerecord_sortable()` принимает аргумент-объект с опциями, аналогичными [виджету JQuery UI Sortable](http://api.jqueryui.com/sortable/). По-умолчанию поле `axis` у аргумента-объекта принимает значение `y` (разрешается только перетаскивание по вертикали), а поле `update` перезаписывается внутренним обработчиком – его переопределить нельзя. +У узла `$('*[data-role=activerecord_sortable]')` можно указать data-атрибут `start-position` – `
...
` – в этом случае позиция первого элемента будет равна значению этого атрибута. Если значение `start-position` не задавать, то будет взято значение data-атрибута `position` первого узла-ребёнка, если и его нет – то `0`. + ## Как добавить функциональность activerecord-sortable в существующую модель ```ruby diff --git a/activerecord-sortable.gemspec b/activerecord-sortable.gemspec index 6c20941..3343047 100644 --- a/activerecord-sortable.gemspec +++ b/activerecord-sortable.gemspec @@ -15,7 +15,9 @@ Gem::Specification.new do |gem| gem.test_files = Dir["spec/**/*"] gem.add_development_dependency 'capybara' - gem.add_development_dependency 'poltergeist' + gem.add_development_dependency 'poltergeist', '1.7.0' + gem.add_development_dependency 'phantomjs', '2.1.1.0' + gem.add_development_dependency 'coveralls' gem.add_development_dependency 'rubocop' end diff --git a/lib/activerecord/sortable/acts_as_sortable.rb b/lib/activerecord/sortable/acts_as_sortable.rb index b9f3882..9fd0502 100644 --- a/lib/activerecord/sortable/acts_as_sortable.rb +++ b/lib/activerecord/sortable/acts_as_sortable.rb @@ -8,14 +8,14 @@ module ActsAsSortable extend ::ActiveSupport::Concern module ClassMethods - def acts_as_sortable(&block) + def acts_as_sortable options = { relation: ->(instance) { instance.class }, append: false, position_column: :position, touch: true } - block.call(options) if block_given? + yield(options) if block_given? sortable_relation_create_accessors sortable_relation_provide_accessor_values(options) diff --git a/lib/activerecord/sortable/version.rb b/lib/activerecord/sortable/version.rb index 7eb9113..7dc2e5c 100644 --- a/lib/activerecord/sortable/version.rb +++ b/lib/activerecord/sortable/version.rb @@ -1,5 +1,5 @@ module ActiveRecord module Sortable - VERSION = '0.0.8' + VERSION = '0.0.8'.freeze end end diff --git a/lib/assets/javascripts/sortable.js b/lib/assets/javascripts/sortable.js index 5364e35..54a96ce 100644 --- a/lib/assets/javascripts/sortable.js +++ b/lib/assets/javascripts/sortable.js @@ -93,6 +93,7 @@ function Sortable(node, options) { this.node = node; + this.start_position = this.node.data('start-position') || this.node.children().eq(0).data('position') || 0; if (!options) options = {}; options['update'] = $.proxy(this.sortable_stop_handler, this); @@ -107,7 +108,7 @@ this.node.sortable('disable').trigger('sortable:start'); var node = ui.item; - var new_position = node.index(); + var new_position = this.start_position + node.index(); var old_position = parseInt(node.data('position')); var positions_updater = new PositionsUpdater(this.node, node, new_position, old_position); diff --git a/spec/dummy-3.2/app/controllers/things_controller.rb b/spec/dummy-3.2/app/controllers/things_controller.rb index fa691f9..6f190f4 100644 --- a/spec/dummy-3.2/app/controllers/things_controller.rb +++ b/spec/dummy-3.2/app/controllers/things_controller.rb @@ -1,6 +1,7 @@ class ThingsController < ApplicationController def index @things = Thing.ordered_by_position_asc + @things = @things.where('position >= ?', params[:min_position]) if params[:min_position] end def move diff --git a/spec/dummy-4.0/app/controllers/things_controller.rb b/spec/dummy-4.0/app/controllers/things_controller.rb index fa691f9..6f190f4 100644 --- a/spec/dummy-4.0/app/controllers/things_controller.rb +++ b/spec/dummy-4.0/app/controllers/things_controller.rb @@ -1,6 +1,7 @@ class ThingsController < ApplicationController def index @things = Thing.ordered_by_position_asc + @things = @things.where('position >= ?', params[:min_position]) if params[:min_position] end def move diff --git a/spec/dummy-4.1/app/controllers/things_controller.rb b/spec/dummy-4.1/app/controllers/things_controller.rb index fa691f9..6f190f4 100644 --- a/spec/dummy-4.1/app/controllers/things_controller.rb +++ b/spec/dummy-4.1/app/controllers/things_controller.rb @@ -1,6 +1,7 @@ class ThingsController < ApplicationController def index @things = Thing.ordered_by_position_asc + @things = @things.where('position >= ?', params[:min_position]) if params[:min_position] end def move diff --git a/spec/dummy-4.2/app/controllers/things_controller.rb b/spec/dummy-4.2/app/controllers/things_controller.rb index fa691f9..6f190f4 100644 --- a/spec/dummy-4.2/app/controllers/things_controller.rb +++ b/spec/dummy-4.2/app/controllers/things_controller.rb @@ -1,6 +1,7 @@ class ThingsController < ApplicationController def index @things = Thing.ordered_by_position_asc + @things = @things.where('position >= ?', params[:min_position]) if params[:min_position] end def move diff --git a/spec/features/drag_and_drop_spec.rb b/spec/features/drag_and_drop_spec.rb index be1d500..c8d9fab 100644 --- a/spec/features/drag_and_drop_spec.rb +++ b/spec/features/drag_and_drop_spec.rb @@ -4,78 +4,154 @@ context 'existing things' do before(:each) { Thing.delete_all } - let!(:thing1) { Thing.create } - let!(:thing2) { Thing.create } + context 'all things at one page' do + let!(:thing1) { Thing.create! } + let!(:thing2) { Thing.create! } + + describe 'drag up' do + let(:thing1_selector) { "li[data-role=thing#{thing1.id}]" } + let(:thing2_selector) { "li[data-role=thing#{thing2.id}]" } + + subject do + visit '/' + page.execute_script " + $.getScript('/assets/jquery.simulate.js', function(){ + var draggable = $('#{thing1_selector}'); + var droppable = $('#{thing2_selector}'); + var dy = droppable.offset().top - draggable.offset().top - 1; + + draggable.simulate('drag', {dx:0, dy: dy}); + }); + " + sleep 3 + + click_button 'Refresh' + end + + it 'change first thing position to 0' do + subject + expect(page).to have_selector(:xpath, "//li[@data-role='thing#{thing1.id}' and @data-position='0']") + end + + it 'change second thing position to 1' do + subject + expect(page).to have_selector(:xpath, "//li[@data-role='thing#{thing2.id}' and @data-position='1']") + end + end - describe 'drag up' do - let(:thing1_selector) { "li[data-role=thing#{thing1.id}]" } - let(:thing2_selector) { "li[data-role=thing#{thing2.id}]" } + describe 'drag down' do + let!(:thing1) { Thing.create! } + let!(:thing2) { Thing.create! } - subject do - sleep 3 - page.execute_script " - $.getScript('/assets/jquery.simulate.js', function(){ - var draggable = $('#{thing1_selector}'); - var droppable = $('#{thing2_selector}'); - var dy = droppable.offset().top - draggable.offset().top - 1; + let(:thing1_selector) { "li[data-role=thing#{thing1.id}]" } + let(:thing2_selector) { "li[data-role=thing#{thing2.id}]" } - draggable.simulate('drag', {dx:0, dy: dy}); - }); - " - sleep 3 + subject do + sleep 3 - click_button 'Refresh' - end + delta = thing2.sortable_append ? -1 : 1 + page.execute_script " + $.getScript('/assets/jquery.simulate.js', function(){ + var draggable = $('#{thing2_selector}'); + var droppable = $('#{thing1_selector}'); - it 'change first thing position to 0' do - visit '/' - subject - expect(page).to have_selector(:xpath, "//li[@data-role='thing#{thing1.id}' and @data-position='0']") - end + var dy = droppable.offset().top - draggable.offset().top + #{delta}; - it 'change second thing position to 1' do - visit '/' - subject - expect(page).to have_selector(:xpath, "//li[@data-role='thing#{thing2.id}' and @data-position='1']") + draggable.simulate('drag', {dx:0, dy: dy}); + }); + " + sleep 3 + + click_button 'Refresh' + end + + it 'change first thing position to 0' do + visit '/' + subject + expect(page).to have_selector(:xpath, "//li[@data-role='thing#{thing1.id}' and @data-position='#{thing1.sortable_append ? 1 : 0}']") + end + + it 'change second thing position to 1' do + visit '/' + subject + expect(page).to have_selector(:xpath, "//li[@data-role='thing#{thing2.id}' and @data-position='#{thing2.sortable_append ? 0 : 1}']") + end end end - describe 'drag down' do - let!(:thing1) { Thing.create } - let!(:thing2) { Thing.create } + # In some cases first thing position may be greater than 0 (pagination, for example) + context 'second page' do + describe 'drag up' do + let!(:thing0) { Thing.create! } + let!(:thing1) { Thing.create! } + let!(:thing2) { Thing.create! } + + let(:thing1_selector) { "li[data-role=thing#{thing1.id}]" } + let(:thing2_selector) { "li[data-role=thing#{thing2.id}]" } + + subject do + visit '/?min_position=1' + page.execute_script " + $.getScript('/assets/jquery.simulate.js', function(){ + var draggable = $('#{thing1_selector}'); + var droppable = $('#{thing2_selector}'); + var dy = droppable.offset().top - draggable.offset().top - 1; + + draggable.simulate('drag', {dx:0, dy: dy}); + }); + " + sleep 3 + + click_button 'Refresh' + end + + it 'change first thing position to 1' do + subject + expect(page).to have_selector(:xpath, "//li[@data-role='thing#{thing1.id}' and @data-position='1']") + end + + it 'change second thing position to 2' do + subject + expect(page).to have_selector(:xpath, "//li[@data-role='thing#{thing2.id}' and @data-position='2']") + end + end - let(:thing1_selector) { "li[data-role=thing#{thing1.id}]" } - let(:thing2_selector) { "li[data-role=thing#{thing2.id}]" } + describe 'drag down' do + let!(:thing0) { Thing.create! } + let!(:thing1) { Thing.create! } + let!(:thing2) { Thing.create! } - subject do - sleep 3 + let(:thing1_selector) { "li[data-role=thing#{thing1.id}]" } + let(:thing2_selector) { "li[data-role=thing#{thing2.id}]" } - delta = thing2.sortable_append ? -1 : 1 - page.execute_script " - $.getScript('/assets/jquery.simulate.js', function(){ - var draggable = $('#{thing2_selector}'); - var droppable = $('#{thing1_selector}'); + subject do + visit '/?min_position=1' - var dy = droppable.offset().top - draggable.offset().top + #{delta}; + delta = thing2.sortable_append ? -1 : 1 + page.execute_script " + $.getScript('/assets/jquery.simulate.js', function(){ + var draggable = $('#{thing2_selector}'); + var droppable = $('#{thing1_selector}'); - draggable.simulate('drag', {dx:0, dy: dy}); - }); - " - sleep 3 + var dy = droppable.offset().top - draggable.offset().top + #{delta}; - click_button 'Refresh' - end + draggable.simulate('drag', {dx:0, dy: dy}); + }); + " + sleep 3 - it 'change first thing position to 0' do - visit '/' - subject - expect(page).to have_selector(:xpath, "//li[@data-role='thing#{thing1.id}' and @data-position='#{thing1.sortable_append ? 1 : 0}']") - end + click_button 'Refresh' + end - it 'change second thing position to 1' do - visit '/' - subject - expect(page).to have_selector(:xpath, "//li[@data-role='thing#{thing2.id}' and @data-position='#{thing2.sortable_append ? 0 : 1}']") + it 'change first thing position to 1' do + subject + expect(page).to have_selector(:xpath, "//li[@data-role='thing#{thing1.id}' and @data-position='#{thing1.sortable_append ? 2 : 1}']") + end + + it 'change second thing position to 2' do + subject + expect(page).to have_selector(:xpath, "//li[@data-role='thing#{thing2.id}' and @data-position='#{thing2.sortable_append ? 1 : 2}']") + end end end end @@ -88,7 +164,7 @@ let(:child2_selector) { "li[data-position='0']" } subject do - sleep 3 + visit new_parent_path page.execute_script %{ $.getScript('/assets/jquery.simulate.js', function(){ var draggable = $("#{child1_selector}"); @@ -104,13 +180,11 @@ end it 'change first child position to 2' do - visit new_parent_path subject expect(page).to have_selector("li[data-position='1']", text: 'Child 0') end it 'change last child position to 0' do - visit new_parent_path subject expect(page).to have_selector("li[data-position='0']", text: 'Child 2') end @@ -121,7 +195,7 @@ let(:child2_selector) { "li[data-position='0']" } subject do - sleep 3 + visit new_parent_path page.execute_script %{ $.getScript('/assets/jquery.simulate.js', function(){ var draggable = $("#{child2_selector}"); @@ -137,19 +211,16 @@ end it 'change first child position to 2' do - visit new_parent_path subject expect(page).to have_selector("li[data-position='2']", text: 'Child 0') end it 'change middle child position to 0' do - visit new_parent_path subject expect(page).to have_selector("li[data-position='0']", text: 'Child 1') end it 'change last child position to 1' do - visit new_parent_path subject expect(page).to have_selector("li[data-position='1']", text: 'Child 2') end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 91cc7e2..1ab05ee 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,6 +11,7 @@ require 'capybara/rspec' require 'capybara/rails' require 'capybara/poltergeist' +require 'phantomjs/poltergeist' Capybara.javascript_driver = :poltergeist Capybara.app = Dummy::Application