activeadmin_sortable_table 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +15 -0
  5. data/.travis.yml +20 -0
  6. data/Gemfile +8 -0
  7. data/Gemfile.lock +245 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +97 -0
  10. data/Rakefile +27 -0
  11. data/activeadmin_sortable_table.gemspec +28 -0
  12. data/app/assets/javascripts/activeadmin_sortable_table.js +35 -0
  13. data/app/assets/stylesheets/activeadmin_sortable_table.css +11 -0
  14. data/lib/active_admin/sortable_table/engine.rb +9 -0
  15. data/lib/active_admin/sortable_table/handle_column.rb +46 -0
  16. data/lib/active_admin/sortable_table/version.rb +6 -0
  17. data/lib/activeadmin_sortable_table.rb +38 -0
  18. data/spec/dummy/README.rdoc +28 -0
  19. data/spec/dummy/Rakefile +6 -0
  20. data/spec/dummy/app/admin/categories.rb +12 -0
  21. data/spec/dummy/app/admin/dashboard.rb +32 -0
  22. data/spec/dummy/app/assets/images/.keep +0 -0
  23. data/spec/dummy/app/assets/javascripts/active_admin.js.coffee +3 -0
  24. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  25. data/spec/dummy/app/assets/javascripts/jquery.simulate.js +314 -0
  26. data/spec/dummy/app/assets/stylesheets/active_admin.scss +17 -0
  27. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  28. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  29. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  30. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  31. data/spec/dummy/app/mailers/.keep +0 -0
  32. data/spec/dummy/app/models/.keep +0 -0
  33. data/spec/dummy/app/models/category.rb +3 -0
  34. data/spec/dummy/app/models/concerns/.keep +0 -0
  35. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  36. data/spec/dummy/bin/bundle +3 -0
  37. data/spec/dummy/bin/rails +4 -0
  38. data/spec/dummy/bin/rake +4 -0
  39. data/spec/dummy/bin/setup +29 -0
  40. data/spec/dummy/config.ru +4 -0
  41. data/spec/dummy/config/application.rb +28 -0
  42. data/spec/dummy/config/boot.rb +5 -0
  43. data/spec/dummy/config/database.yml +25 -0
  44. data/spec/dummy/config/environment.rb +5 -0
  45. data/spec/dummy/config/environments/development.rb +41 -0
  46. data/spec/dummy/config/environments/production.rb +79 -0
  47. data/spec/dummy/config/environments/test.rb +42 -0
  48. data/spec/dummy/config/initializers/active_admin.rb +262 -0
  49. data/spec/dummy/config/initializers/assets.rb +11 -0
  50. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  51. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  52. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  53. data/spec/dummy/config/initializers/inflections.rb +16 -0
  54. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  55. data/spec/dummy/config/initializers/session_store.rb +3 -0
  56. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  57. data/spec/dummy/config/locales/en.yml +23 -0
  58. data/spec/dummy/config/routes.rb +3 -0
  59. data/spec/dummy/config/secrets.yml +22 -0
  60. data/spec/dummy/db/development.sqlite3 +0 -0
  61. data/spec/dummy/db/migrate/20150910072150_create_active_admin_comments.rb +19 -0
  62. data/spec/dummy/db/migrate/20150910072505_create_categories.rb +8 -0
  63. data/spec/dummy/db/schema.rb +36 -0
  64. data/spec/dummy/lib/assets/.keep +0 -0
  65. data/spec/dummy/log/.keep +0 -0
  66. data/spec/dummy/public/404.html +67 -0
  67. data/spec/dummy/public/422.html +67 -0
  68. data/spec/dummy/public/500.html +66 -0
  69. data/spec/dummy/public/favicon.ico +0 -0
  70. data/spec/features/activeadmin_sortable_table_spec.rb +31 -0
  71. data/spec/rails_helper.rb +62 -0
  72. data/spec/spec_helper.rb +92 -0
  73. data/spec/support/wait_for_ajax.rb +20 -0
  74. metadata +288 -0
@@ -0,0 +1,27 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task default: ['dummy:prepare', :spec]
6
+
7
+ require 'rake/clean'
8
+ CLEAN.include 'spec/dummy/db/*sqlite3', 'spec/dummy/log/*', 'spec/dummy/public/assets/*', 'spec/dummy/tmp/**/*'
9
+
10
+ namespace :dummy do
11
+ desc 'Setup dummy app database'
12
+ task :prepare do
13
+ # File.expand_path is executed directory of generated Rails app
14
+ rakefile = File.expand_path('Rakefile', dummy_path)
15
+ command = format("rake -f '%s' db:schema:load RAILS_ENV=test", rakefile)
16
+ sh(command) unless ENV['DISABLE_CREATE']
17
+ end
18
+
19
+ def dummy_path
20
+ rel_path = ENV['DUMMY_APP_PATH'] || 'spec/dummy'
21
+ if @current_path.to_s.include?(rel_path)
22
+ @current_path
23
+ else
24
+ @current_path = File.expand_path(rel_path)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'active_admin/sortable_table/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'activeadmin_sortable_table'
8
+ gem.version = ActiveAdmin::SortableTable::VERSION
9
+ gem.authors = ['Adam McCrea', 'Jonathan Gertig', 'Artyom Bolshakov']
10
+ gem.email = ['adam@adamlogic.com', 'jcgertig@gmail.com', 'abolshakov@spbtv.com']
11
+ gem.description = 'Drag and drop reordering interface for ActiveAdmin tables'
12
+ gem.summary = 'Drag and drop reordering interface for ActiveAdmin tables'
13
+ gem.homepage = 'https://github.com/bolshakov/activeadmin_sortable_table'
14
+
15
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ['lib']
19
+
20
+ gem.add_dependency 'activeadmin', '>= 1.0.0.pre1'
21
+ gem.add_development_dependency 'rails', '~> 4.2'
22
+ gem.add_development_dependency 'capybara'
23
+ gem.add_development_dependency 'rspec-rails', '~> 3.3'
24
+ gem.add_development_dependency 'phantomjs'
25
+ gem.add_development_dependency 'poltergeist'
26
+ gem.add_development_dependency 'database_cleaner'
27
+ gem.add_development_dependency 'launchy'
28
+ end
@@ -0,0 +1,35 @@
1
+ (function($) {
2
+ $(document).ready(function() {
3
+ $('.handle').closest('tbody').activeAdminSortableTable();
4
+ });
5
+
6
+ $.fn.activeAdminSortableTable = function() {
7
+ this.sortable({
8
+ update: function(event, ui) {
9
+ var item = ui.item.find('[data-sort-url]');
10
+ var url = item.data('sort-url');
11
+ var actionOnSuccess = item.data('sort-success-action');
12
+ var customParams = {};
13
+ if (typeof item.data('sort-custom-params') === 'object') {
14
+ customParams = item.data('sort-custom-params');
15
+ }
16
+
17
+ $.ajax({
18
+ url: url,
19
+ type: 'post',
20
+ data: $.extend(customParams, { position: ui.item.index() + 1 }),
21
+ error: function() { console.error('Saving sortable error'); },
22
+ success: function() {
23
+ if (actionOnSuccess === 'noting') { return; }
24
+
25
+ $("tr", $('.handle').closest('tbody')).removeClass('even odd');
26
+ $("tr", $('.handle').closest('tbody')).filter(":even").addClass('odd');
27
+ $("tr", $('.handle').closest('tbody')).filter(":odd").addClass('even');
28
+ }
29
+ });
30
+ }
31
+ });
32
+
33
+ this.disableSelection();
34
+ }
35
+ })(jQuery);
@@ -0,0 +1,11 @@
1
+ .activeadmin_sortable_table .handle {
2
+ cursor: ns-resize;
3
+ position: relative;
4
+ padding-left: 1.25em;
5
+ }
6
+
7
+ .activeadmin_sortable_table .handle:before {
8
+ content: '\2630';
9
+ font-size: 1.5em;
10
+ position: absolute;
11
+ }
@@ -0,0 +1,9 @@
1
+ require 'rails/engine'
2
+
3
+ module ActiveAdmin
4
+ module SortableTable
5
+ # Including an Engine tells Rails that this gem contains assets
6
+ class Engine < ::Rails::Engine
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,46 @@
1
+ module ActiveAdmin
2
+ #
3
+ module SortableTable
4
+ # Adds `handle_column` method to `TableFor` dsl.
5
+ # @example on index page
6
+ # index do
7
+ # handle_column
8
+ # id_column
9
+ # # other columns...
10
+ # end
11
+ #
12
+ # @example table_for
13
+ # table_for c.collection_memberships do
14
+ # handle_column
15
+ # # other columns...
16
+ # end
17
+ #
18
+ module HandleColumn
19
+ # @param [Hash] options
20
+ # @option options [Symbol, Proc, String] :url
21
+ #
22
+ def handle_column(options = {})
23
+ column '', class: 'activeadmin_sortable_table' do |resource|
24
+ content_tag :span, '', class: 'handle', 'data-sort-url' => sort_url(options[:url], resource)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def sort_url(url, resource)
31
+ if url.is_a?(Symbol)
32
+ send(url, resource)
33
+ elsif url.respond_to?(:call)
34
+ url.call(resource)
35
+ else
36
+ sort_url, query_params = resource_path(resource).split('?', 2)
37
+ sort_url += '/sort'
38
+ sort_url += '?' + query_params if query_params
39
+ sort_url
40
+ end
41
+ end
42
+ end
43
+
44
+ ::ActiveAdmin::Views::TableFor.send(:include, HandleColumn)
45
+ end
46
+ end
@@ -0,0 +1,6 @@
1
+ module ActiveAdmin
2
+ #
3
+ module SortableTable
4
+ VERSION = '1.0.0'
5
+ end
6
+ end
@@ -0,0 +1,38 @@
1
+ require 'activeadmin'
2
+
3
+ module ActiveAdmin
4
+ # Include this module to registered page to enable
5
+ # ActiveAdmin Sortable Table extension
6
+ # @example
7
+ #
8
+ # ActiveAdmin.register Category do
9
+ # include ActiveAdmin::SortableTable
10
+ # config.sort_order = 'position_asc'
11
+ # permit_params :position
12
+ #
13
+ # index do
14
+ # handle_column
15
+ # id_column
16
+ # end
17
+ # end
18
+ #
19
+ module SortableTable
20
+ require 'active_admin/sortable_table/version'
21
+ require 'active_admin/sortable_table/engine'
22
+ require 'active_admin/sortable_table/handle_column'
23
+
24
+ class << self
25
+ # @param [ActiveAdmin::DSL] dsl
26
+ # @return [void]
27
+ #
28
+ def included(dsl)
29
+ dsl.instance_eval do
30
+ member_action :sort, method: :post do
31
+ resource.insert_at params[:position].to_i
32
+ head 200
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -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>.
@@ -0,0 +1,6 @@
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
@@ -0,0 +1,12 @@
1
+ ActiveAdmin.register Category do
2
+ include ActiveAdmin::SortableTable
3
+ permit_params :name, :position
4
+ config.sort_order = 'position_asc'
5
+
6
+ index do
7
+ handle_column
8
+ id_column
9
+ column :name
10
+ actions
11
+ end
12
+ end
@@ -0,0 +1,32 @@
1
+ ActiveAdmin.register_page 'Dashboard' do
2
+ menu priority: 1, label: proc { I18n.t('active_admin.dashboard') }
3
+
4
+ content title: proc { I18n.t('active_admin.dashboard') } do
5
+ div class: 'blank_slate_container', id: 'dashboard_default_message' do
6
+ span class: 'blank_slate' do
7
+ span I18n.t('active_admin.dashboard_welcome.welcome')
8
+ small I18n.t('active_admin.dashboard_welcome.call_to_action')
9
+ end
10
+ end
11
+
12
+ # Here is an example of a simple dashboard with columns and panels.
13
+ #
14
+ # columns do
15
+ # column do
16
+ # panel "Recent Posts" do
17
+ # ul do
18
+ # Post.recent(5).map do |post|
19
+ # li link_to(post.title, admin_post_path(post))
20
+ # end
21
+ # end
22
+ # end
23
+ # end
24
+
25
+ # column do
26
+ # panel "Info" do
27
+ # para "Welcome to ActiveAdmin."
28
+ # end
29
+ # end
30
+ # end
31
+ end # content
32
+ end
File without changes
@@ -0,0 +1,3 @@
1
+ #= require active_admin/base
2
+ #= require jquery.simulate
3
+ #= require activeadmin_sortable_table
@@ -0,0 +1,13 @@
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
+ //= require_tree .
@@ -0,0 +1,314 @@
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[ type ] ) {
205
+ elem[ type ]();
206
+ } else if ( elem.dispatchEvent ) {
207
+ elem.dispatchEvent( event );
208
+ } else if ( elem.fireEvent ) {
209
+ elem.fireEvent( "on" + type, event );
210
+ }
211
+ },
212
+
213
+ simulateFocus: function() {
214
+ var focusinEvent,
215
+ triggered = false,
216
+ element = $( this.target );
217
+
218
+ function trigger() {
219
+ triggered = true;
220
+ }
221
+
222
+ element.bind( "focus", trigger );
223
+ element[ 0 ].focus();
224
+
225
+ if ( !triggered ) {
226
+ focusinEvent = $.Event( "focusin" );
227
+ focusinEvent.preventDefault();
228
+ element.trigger( focusinEvent );
229
+ element.triggerHandler( "focus" );
230
+ }
231
+ element.unbind( "focus", trigger );
232
+ },
233
+
234
+ simulateBlur: function() {
235
+ var focusoutEvent,
236
+ triggered = false,
237
+ element = $( this.target );
238
+
239
+ function trigger() {
240
+ triggered = true;
241
+ }
242
+
243
+ element.bind( "blur", trigger );
244
+ element[ 0 ].blur();
245
+
246
+ // blur events are async in IE
247
+ setTimeout(function() {
248
+ // IE won't let the blur occur if the window is inactive
249
+ if ( element[ 0 ].ownerDocument.activeElement === element[ 0 ] ) {
250
+ element[ 0 ].ownerDocument.body.focus();
251
+ }
252
+
253
+ // Firefox won't trigger events if the window is inactive
254
+ // IE doesn't trigger events if we had to manually focus the body
255
+ if ( !triggered ) {
256
+ focusoutEvent = $.Event( "focusout" );
257
+ focusoutEvent.preventDefault();
258
+ element.trigger( focusoutEvent );
259
+ element.triggerHandler( "blur" );
260
+ }
261
+ element.unbind( "blur", trigger );
262
+ }, 1 );
263
+ }
264
+ });
265
+
266
+
267
+
268
+ /** complex events **/
269
+
270
+ function findCenter( elem ) {
271
+ var offset,
272
+ document = $( elem.ownerDocument );
273
+ elem = $( elem );
274
+ offset = elem.offset();
275
+
276
+ return {
277
+ x: offset.left + elem.outerWidth() / 2 - document.scrollLeft(),
278
+ y: offset.top + elem.outerHeight() / 2 - document.scrollTop()
279
+ };
280
+ }
281
+
282
+ $.extend( $.simulate.prototype, {
283
+ simulateDrag: function() {
284
+ var i = 0,
285
+ target = this.target,
286
+ options = this.options,
287
+ center = findCenter( target ),
288
+ x = Math.floor( center.x ),
289
+ y = Math.floor( center.y ),
290
+ dx = options.dx || 0,
291
+ dy = options.dy || 0,
292
+ moves = options.moves || 3,
293
+ coord = { clientX: x, clientY: y };
294
+
295
+ this.simulateEvent( target, "mousedown", coord );
296
+
297
+ for ( ; i < moves ; i++ ) {
298
+ x += dx / moves;
299
+ y += dy / moves;
300
+
301
+ coord = {
302
+ clientX: Math.round( x ),
303
+ clientY: Math.round( y )
304
+ };
305
+
306
+ this.simulateEvent( document, "mousemove", coord );
307
+ }
308
+
309
+ this.simulateEvent( target, "mouseup", coord );
310
+ this.simulateEvent( target, "click", coord );
311
+ }
312
+ });
313
+
314
+ })( jQuery );